Image Generation: Add swipes for generated images

Supersedes #2648
This commit is contained in:
Cohee
2024-08-13 23:21:00 +03:00
parent 78bee4631d
commit e1a29b36f5
4 changed files with 146 additions and 1 deletions

View File

@ -5801,6 +5801,11 @@
<div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div> <div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div>
<div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div> <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div>
</div> </div>
<div class="mes_img_swipes">
<div title="Swipe left" class="right_menu_button fa-lg fa-solid fa-chevron-left mes_img_swipe_left" data-i18n="[title]Swipe left"></div>
<div class="mes_img_swipe_counter">1/1</div>
<div title="Swipe right" class="right_menu_button fa-lg fa-solid fa-chevron-right mes_img_swipe_right" data-i18n="[title]Swipe right"></div>
</div>
<img class="mes_img" src="" /> <img class="mes_img" src="" />
</div> </div>
<div class="mes_bias"></div> <div class="mes_bias"></div>

View File

@ -459,6 +459,7 @@ export const event_types = {
LLM_FUNCTION_TOOL_REGISTER: 'llm_function_tool_register', LLM_FUNCTION_TOOL_REGISTER: 'llm_function_tool_register',
LLM_FUNCTION_TOOL_CALL: 'llm_function_tool_call', LLM_FUNCTION_TOOL_CALL: 'llm_function_tool_call',
ONLINE_STATUS_CHANGED: 'online_status_changed', ONLINE_STATUS_CHANGED: 'online_status_changed',
IMAGE_SWIPED: 'image_swiped',
}; };
export const eventSource = new EventEmitter(); export const eventSource = new EventEmitter();
@ -2112,6 +2113,7 @@ export function updateMessageBlock(messageId, message) {
export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
// Add image to message // Add image to message
if (mes.extra?.image) { if (mes.extra?.image) {
const container = messageElement.find('.mes_img_container');
const chatHeight = $('#chat').prop('scrollHeight'); const chatHeight = $('#chat').prop('scrollHeight');
const image = messageElement.find('.mes_img'); const image = messageElement.find('.mes_img');
const text = messageElement.find('.mes_text'); const text = messageElement.find('.mes_text');
@ -2127,9 +2129,27 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) {
}); });
image.attr('src', mes.extra?.image); image.attr('src', mes.extra?.image);
image.attr('title', mes.extra?.title || mes.title || ''); image.attr('title', mes.extra?.title || mes.title || '');
messageElement.find('.mes_img_container').addClass('img_extra'); container.addClass('img_extra');
image.toggleClass('img_inline', isInline); image.toggleClass('img_inline', isInline);
text.toggleClass('displayNone', !isInline); text.toggleClass('displayNone', !isInline);
const imageSwipes = mes.extra.image_swipes;
if (Array.isArray(imageSwipes) && imageSwipes.length > 0) {
container.addClass('img_swipes');
const counter = container.find('.mes_img_swipe_counter');
const currentImage = imageSwipes.indexOf(mes.extra.image) + 1;
counter.text(`${currentImage}/${imageSwipes.length}`);
const swipeLeft = container.find('.mes_img_swipe_left');
swipeLeft.off('click').on('click', function () {
eventSource.emit(event_types.IMAGE_SWIPED, { message: mes, element: messageElement, direction: 'left' });
});
const swipeRight = container.find('.mes_img_swipe_right');
swipeRight.off('click').on('click', function () {
eventSource.emit(event_types.IMAGE_SWIPED, { message: mes, element: messageElement, direction: 'right' });
});
}
} }
// Add file to message // Add file to message

View File

@ -59,6 +59,7 @@ const initiators = {
action: 'action', action: 'action',
interactive: 'interactive', interactive: 'interactive',
wand: 'wand', wand: 'wand',
swipe: 'swipe',
}; };
const generationMode = { const generationMode = {
@ -3429,6 +3430,7 @@ async function sendMessage(prompt, image, generationType, additionalNegativePref
generationType: generationType, generationType: generationType,
negative: additionalNegativePrefix, negative: additionalNegativePrefix,
inline_image: false, inline_image: false,
image_swipes: [image],
}, },
}; };
context.chat.push(message); context.chat.push(message);
@ -3657,6 +3659,99 @@ async function writePromptFields(characterId) {
await writeExtensionField(characterId, 'sd_character_prompt', promptObject); await writeExtensionField(characterId, 'sd_character_prompt', promptObject);
} }
/**
* Switches an image to the next or previous one in the swipe list.
* @param {object} args Event arguments
* @param {any} args.message Message object
* @param {JQuery<HTMLElement>} args.element Message element
* @param {string} args.direction Swipe direction
* @returns {Promise<void>}
*/
async function onImageSwiped({ message, element, direction }) {
const context = getContext();
const animationClass = 'fa-fade';
const messageImg = element.find('.mes_img');
// Current image is already animating
if (messageImg.hasClass(animationClass)) {
return;
}
const swipes = message?.extra?.image_swipes;
if (!Array.isArray(swipes)) {
console.warn('No image swipes found in the message');
return;
}
const currentIndex = swipes.indexOf(message.extra.image);
if (currentIndex === -1) {
console.warn('Current image not found in the swipes');
return;
}
// Switch to previous image or wrap around if at the beginning
if (direction === 'left') {
const newIndex = currentIndex === 0 ? swipes.length - 1 : currentIndex - 1;
message.extra.image = swipes[newIndex];
// Update the image in the message
appendMediaToMessage(message, element, false);
}
// Switch to next image or generate a new one if at the end
if (direction === 'right') {
const newIndex = currentIndex === swipes.length - 1 ? swipes.length : currentIndex + 1;
if (newIndex === swipes.length) {
const abortController = new AbortController();
const swipeControls = element.find('.mes_img_swipes');
const stopButton = document.getElementById('sd_stop_gen');
const stopListener = () => abortController.abort('Aborted by user');
const generationType = message?.extra?.generationType ?? generationMode.FREE;
const dimensions = setTypeSpecificDimensions(generationType);
const originalSeed = extension_settings.sd.seed;
extension_settings.sd.seed = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
let imagePath = '';
try {
$(stopButton).show();
eventSource.once(CUSTOM_STOP_EVENT, stopListener);
const callback = () => { };
const hasNegative = message.extra.negative;
const prompt = await refinePrompt(message.extra.title, false, false);
const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, false, true) : '';
const characterName = context.characterId
? context.characters[context.characterId].name
: context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString();
messageImg.addClass(animationClass);
swipeControls.hide();
imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiators.swipe, abortController.signal);
} finally {
$(stopButton).hide();
messageImg.removeClass(animationClass);
swipeControls.show();
eventSource.removeListener(CUSTOM_STOP_EVENT, stopListener);
restoreOriginalDimensions(dimensions);
extension_settings.sd.seed = originalSeed;
}
if (!imagePath) {
return;
}
swipes.push(imagePath);
}
message.extra.image = swipes[newIndex];
appendMediaToMessage(message, element, false);
}
await context.saveChat();
}
jQuery(async () => { jQuery(async () => {
await addSDGenButtons(); await addSDGenButtons();
@ -3795,6 +3890,8 @@ jQuery(async () => {
} }
}); });
eventSource.on(event_types.IMAGE_SWIPED, onImageSwiped);
eventSource.on(event_types.CHAT_CHANGED, onChatChanged); eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
await loadSettings(); await loadSettings();

View File

@ -4569,6 +4569,7 @@ a {
image-rendering: -webkit-optimize-contrast; image-rendering: -webkit-optimize-contrast;
} }
.mes_img_swipes,
.mes_img_controls { .mes_img_controls {
position: absolute; position: absolute;
top: 0.1em; top: 0.1em;
@ -4578,9 +4579,16 @@ a {
opacity: 0; opacity: 0;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center;
padding: 1em; padding: 1em;
} }
.mes_img_swipes {
top: unset;
bottom: 0.1rem;
}
.mes_img_swipes .right_menu_button,
.mes_img_controls .right_menu_button { .mes_img_controls .right_menu_button {
filter: brightness(90%); filter: brightness(90%);
text-shadow: 1px 1px var(--SmartThemeShadowColor) !important; text-shadow: 1px 1px var(--SmartThemeShadowColor) !important;
@ -4589,16 +4597,20 @@ a {
width: 1.25em; width: 1.25em;
} }
.mes_img_swipes .right_menu_button::before,
.mes_img_controls .right_menu_button::before { .mes_img_controls .right_menu_button::before {
/* Fix weird alignment with this font-awesome icons on focus */ /* Fix weird alignment with this font-awesome icons on focus */
position: relative; position: relative;
top: 0.6125em; top: 0.6125em;
} }
.mes_img_swipes .right_menu_button:hover,
.mes_img_controls .right_menu_button:hover { .mes_img_controls .right_menu_button:hover {
filter: brightness(150%); filter: brightness(150%);
} }
.mes_img_container:hover .mes_img_swipes,
.mes_img_container:focus-within .mes_img_swipes,
.mes_img_container:hover .mes_img_controls, .mes_img_container:hover .mes_img_controls,
.mes_img_container:focus-within .mes_img_controls { .mes_img_container:focus-within .mes_img_controls {
opacity: 1; opacity: 1;
@ -4612,6 +4624,17 @@ body:not(.caption) .mes_img_caption {
display: none; display: none;
} }
.mes_img_container:not(.img_swipes) .mes_img_swipes,
body:not(.sd) .mes_img_swipes {
display: none;
}
.mes_img_swipe_counter {
font-weight: 600;
filter: drop-shadow(2px 4px 6px black);
cursor: default;
}
.img_enlarged_holder { .img_enlarged_holder {
/* Scaling via flex-grow and object-fit only works if we have some kind of base-height set */ /* Scaling via flex-grow and object-fit only works if we have some kind of base-height set */
min-height: 120px; min-height: 120px;