Add captioning for video attachments (#4749) * Add captioning for video attachments * Unify error toast titles * Add MEDIA_SOURCE enum and update media handling to include source information * Unify attachment handling logic * Add error handling for auto-captioning failures * Use string formatting for console error

38679897c6193a7fbf547fb0d2bb5191aee2b3da

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
8 files changed, +116 -35Ignore whitespace
public/global.d.ts+4 -0
@@ -70,6 +70,7 @@ declare global {
70 url: string;70 url: string;
71 title?: string;71 title?: string;
72 type: string;72 type: string;
73 source?: string;
73 }74 }
7475
75 interface ImageGenerationAttachmentProps {76 interface ImageGenerationAttachmentProps {
@@ -78,7 +79,10 @@ declare global {
78 }79 }
7980
80 interface ImageCaptionAttachmentProps {81 interface ImageCaptionAttachmentProps {
82 /** Append title to the message text in case of non-inline captions. */
81 append_title?: boolean;83 append_title?: boolean;
84 /** Marker for captioned images to prevent auto-caption from firing again. */
85 captioned?: boolean;
82 }86 }
8387
84 // Global namespace modules88 // Global namespace modules
public/index.html+1 -0
@@ -7295,6 +7295,7 @@
7295 <div class="mes_media_container mes_video_container">7295 <div class="mes_media_container mes_video_container">
7296 <div class="mes_video_controls">7296 <div class="mes_video_controls">
7297 <div title="Expand and zoom" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_media_enlarge" data-i18n="[title]Expand and zoom"></div>7297 <div title="Expand and zoom" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_media_enlarge" data-i18n="[title]Expand and zoom"></div>
7298 <div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div>
7298 <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_media_delete" data-i18n="[title]Delete"></div>7299 <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_media_delete" data-i18n="[title]Delete"></div>
7299 </div>7300 </div>
7300 <video class="mes_video" controls preload="metadata"></video>7301 <video class="mes_video" controls preload="metadata"></video>
public/script.js+2 -2
@@ -183,7 +183,7 @@ import {
183 trimSpaces,183 trimSpaces,
184 clamp,184 clamp,
185} from './scripts/utils.js';185} from './scripts/utils.js';
186import { debounce_timeout, GENERATION_TYPE_TRIGGERS, IGNORE_SYMBOL, inject_ids, MEDIA_DISPLAY, MEDIA_TYPE, SCROLL_BEHAVIOR, SWIPE_DIRECTION } from './scripts/constants.js';186import { debounce_timeout, GENERATION_TYPE_TRIGGERS, IGNORE_SYMBOL, inject_ids, MEDIA_DISPLAY, MEDIA_SOURCE, MEDIA_TYPE, SCROLL_BEHAVIOR, SWIPE_DIRECTION } from './scripts/constants.js';
187187
188import { cancelDebouncedMetadataSave, doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';188import { cancelDebouncedMetadataSave, doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';
189import { COMMENT_NAME_DEFAULT, CONNECT_API_MAP, executeSlashCommandsOnChatInput, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, stopScriptExecution, UNIQUE_APIS } from './scripts/slash-commands.js';189import { COMMENT_NAME_DEFAULT, CONNECT_API_MAP, executeSlashCommandsOnChatInput, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, stopScriptExecution, UNIQUE_APIS } from './scripts/slash-commands.js';
@@ -6414,7 +6414,7 @@ function saveImageToMessage(img, mes) {
6414 if (!Array.isArray(mes.extra.media)) {6414 if (!Array.isArray(mes.extra.media)) {
6415 mes.extra.media = [];6415 mes.extra.media = [];
6416 }6416 }
6417 mes.extra.media.push({ url: img.image, type: MEDIA_TYPE.IMAGE, title: img.title });6417 mes.extra.media.push({ url: img.image, type: MEDIA_TYPE.IMAGE, title: img.title, source: MEDIA_SOURCE.API });
6418 mes.extra.inline_image = img.inline;6418 mes.extra.inline_image = img.inline;
6419 }6419 }
6420}6420}
public/scripts/chats.js+9 -2
@@ -57,7 +57,7 @@ import { renderTemplateAsync } from './templates.js';
57import { t } from './i18n.js';57import { t } from './i18n.js';
58import { humanizedDateTime } from './RossAscends-mods.js';58import { humanizedDateTime } from './RossAscends-mods.js';
59import { accountStorage } from './util/AccountStorage.js';59import { accountStorage } from './util/AccountStorage.js';
60import { MEDIA_DISPLAY, MEDIA_TYPE, SCROLL_BEHAVIOR, SWIPE_DIRECTION } from './constants.js';60import { MEDIA_DISPLAY, MEDIA_SOURCE, MEDIA_TYPE, SCROLL_BEHAVIOR, SWIPE_DIRECTION } from './constants.js';
6161
62/**62/**
63 * @typedef {Object} FileAttachment63 * @typedef {Object} FileAttachment
@@ -216,7 +216,14 @@ export async function populateFileAttachment(message, inputId = 'file_form_input
216 if (!Array.isArray(message.extra.media)) {216 if (!Array.isArray(message.extra.media)) {
217 message.extra.media = [];217 message.extra.media = [];
218 }218 }
219 message.extra.media.push({ url: imageUrl, type: mediaType, title: file.name });219 /** @type {MediaAttachment} */
220 const mediaAttachment = {
221 url: imageUrl,
222 type: mediaType,
223 title: file.name,
224 source: MEDIA_SOURCE.UPLOAD,
225 };
226 message.extra.media.push(mediaAttachment);
220 message.extra.media_index = message.extra.media.length - 1;227 message.extra.media_index = message.extra.media.length - 1;
221 message.extra.inline_image = true;228 message.extra.inline_image = true;
222 } else {229 } else {
public/scripts/constants.js+11 -0
@@ -71,6 +71,17 @@ export const COMETAPI_IGNORE_PATTERNS = [
71 * @enum {string}71 * @enum {string}
72 * @readonly72 * @readonly
73 */73 */
74export const MEDIA_SOURCE = {
75 API: 'api',
76 UPLOAD: 'upload',
77 GENERATED: 'generated',
78 CAPTIONED: 'captioned',
79};
80
81/**
82 * @enum {string}
83 * @readonly
84 */
74export const MEDIA_DISPLAY = {85export const MEDIA_DISPLAY = {
75 LIST: 'list',86 LIST: 'list',
76 GALLERY: 'gallery',87 GALLERY: 'gallery',
public/scripts/extensions/caption/index.js+82 -27
@@ -10,7 +10,7 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js';
10import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';10import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
11import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';11import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
12import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';12import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
13import { MEDIA_DISPLAY, MEDIA_TYPE, SCROLL_BEHAVIOR } from '../../constants.js';13import { MEDIA_DISPLAY, MEDIA_SOURCE, MEDIA_TYPE, SCROLL_BEHAVIOR } from '../../constants.js';
14export { MODULE_NAME };14export { MODULE_NAME };
1515
16const MODULE_NAME = 'caption';16const MODULE_NAME = 'caption';
@@ -135,14 +135,18 @@ async function captionExistingMessage(message, mediaIndex) {
135135
136 const mediaAttachment = message.extra.media[mediaIndex];136 const mediaAttachment = message.extra.media[mediaIndex];
137137
138 if (!mediaAttachment || !mediaAttachment.url || mediaAttachment.type === MEDIA_TYPE.VIDEO) {138 if (!mediaAttachment || !mediaAttachment.url || mediaAttachment.type === MEDIA_TYPE.AUDIO) {
139 return;139 return;
140 }140 }
141141
142 if (mediaAttachment.type === MEDIA_TYPE.VIDEO && !isVideoCaptioningAvailable()) {
143 throw new Error('Captioning videos is not supported for the current source.');
144 }
145
142 const imageData = await fetch(mediaAttachment.url);146 const imageData = await fetch(mediaAttachment.url);
143 const blob = await imageData.blob();147 const blob = await imageData.blob();
144 const type = imageData.headers.get('Content-Type');148 const fileName = mediaAttachment.url.split('/').pop().split('?')[0] || 'image.jpg';
145 const file = new File([blob], 'image.png', { type });149 const file = new File([blob], fileName, { type: blob.type });
146 const caption = await getCaptionForFile(file, null, true);150 const caption = await getCaptionForFile(file, null, true);
147151
148 if (!caption) {152 if (!caption) {
@@ -158,11 +162,12 @@ async function captionExistingMessage(message, mediaIndex) {
158 message.extra.inline_image = false;162 message.extra.inline_image = false;
159 message.mes = wrappedCaption;163 message.mes = wrappedCaption;
160 mediaAttachment.title = wrappedCaption;164 mediaAttachment.title = wrappedCaption;
161 }165 mediaAttachment.captioned = true;
162 else {166 } else {
163 message.extra.inline_image = true;167 message.extra.inline_image = true;
164 mediaAttachment.append_title = true;168 mediaAttachment.append_title = true;
165 mediaAttachment.title = wrappedCaption;169 mediaAttachment.title = wrappedCaption;
170 mediaAttachment.captioned = true;
166 }171 }
167}172}
168173
@@ -170,8 +175,10 @@ async function captionExistingMessage(message, mediaIndex) {
170 * Sends a captioned message to the chat.175 * Sends a captioned message to the chat.
171 * @param {string} caption Caption text176 * @param {string} caption Caption text
172 * @param {string} image Image URL177 * @param {string} image Image URL
178 * @param {string} mimeType Image MIME type
179 * @returns {Promise<void>}
173 */180 */
174async function sendCaptionedMessage(caption, image) {181async function sendCaptionedMessage(caption, image, mimeType) {
175 const messageText = await wrapCaptionTemplate(caption);182 const messageText = await wrapCaptionTemplate(caption);
176183
177 const context = getContext();184 const context = getContext();
@@ -179,8 +186,10 @@ async function sendCaptionedMessage(caption, image) {
179 /** @type {MediaAttachment} */186 /** @type {MediaAttachment} */
180 const mediaAttachment = {187 const mediaAttachment = {
181 url: image,188 url: image,
182 type: MEDIA_TYPE.IMAGE,189 type: MEDIA_TYPE.getFromMime(mimeType) || MEDIA_TYPE.IMAGE,
183 title: messageText,190 title: messageText,
191 captioned: true,
192 source: MEDIA_SOURCE.CAPTIONED,
184 };193 };
185 /** @type {ChatMessage} */194 /** @type {ChatMessage} */
186 const message = {195 const message = {
@@ -351,6 +360,10 @@ async function onSelectImage(e, prompt, quiet) {
351 */360 */
352async function getCaptionForFile(file, prompt, quiet) {361async function getCaptionForFile(file, prompt, quiet) {
353 try {362 try {
363 if (file.type.startsWith('video/') && !isVideoCaptioningAvailable()) {
364 throw new Error('Video captioning is not available for the current source.');
365 }
366
354 setSpinnerIcon();367 setSpinnerIcon();
355 const context = getContext();368 const context = getContext();
356 const fileData = await getBase64Async(await ensureImageFormatSupported(file));369 const fileData = await getBase64Async(await ensureImageFormatSupported(file));
@@ -359,13 +372,13 @@ async function getCaptionForFile(file, prompt, quiet) {
359 const { caption } = await doCaptionRequest(base64Data, fileData, prompt);372 const { caption } = await doCaptionRequest(base64Data, fileData, prompt);
360 if (!quiet) {373 if (!quiet) {
361 const imagePath = await saveBase64AsFile(base64Data, context.name2, '', extension);374 const imagePath = await saveBase64AsFile(base64Data, context.name2, '', extension);
362 await sendCaptionedMessage(caption, imagePath);375 await sendCaptionedMessage(caption, imagePath, file.type);
363 }376 }
364 return caption;377 return caption;
365 }378 }
366 catch (error) {379 catch (error) {
367 const errorMessage = error.message || 'Unknown error';380 const errorMessage = error.message || 'Unknown error';
368 toastr.error(errorMessage, 'Failed to caption image.');381 toastr.error(errorMessage, 'Failed to caption');
369 console.error(error);382 console.error(error);
370 return '';383 return '';
371 }384 }
@@ -399,13 +412,18 @@ async function captionCommandCallback(args, prompt) {
399 toastr.error('The specified message does not contain an image.');412 toastr.error('The specified message does not contain an image.');
400 return '';413 return '';
401 }414 }
402 if (mediaAttachment.type === MEDIA_TYPE.VIDEO) {415 if (mediaAttachment.type === MEDIA_TYPE.AUDIO) {
403 toastr.error('The specified media is a video. Captioning videos is not supported.');416 toastr.error('The specified media is an audio file. Captioning audio files is not supported.');
417 return '';
418 }
419 if (mediaAttachment.type === MEDIA_TYPE.VIDEO && !isVideoCaptioningAvailable()) {
420 toastr.error('The specified media is a video. Captioning videos is not supported for the current source.');
404 return '';421 return '';
405 }422 }
406 const fetchResult = await fetch(mediaAttachment.url);423 const fetchResult = await fetch(mediaAttachment.url);
407 const blob = await fetchResult.blob();424 const blob = await fetchResult.blob();
408 const file = new File([blob], 'image.jpg', { type: blob.type });425 const fileName = mediaAttachment.url.split('/').pop().split('?')[0] || 'image.jpg';
426 const file = new File([blob], fileName, { type: blob.type });
409 return await getCaptionForFile(file, prompt, quiet);427 return await getCaptionForFile(file, prompt, quiet);
410 } catch (error) {428 } catch (error) {
411 toastr.error('Failed to get image from the message. Make sure the image is accessible.');429 toastr.error('Failed to get image from the message. Make sure the image is accessible.');
@@ -417,7 +435,7 @@ async function captionCommandCallback(args, prompt) {
417 return new Promise(resolve => {435 return new Promise(resolve => {
418 const input = document.createElement('input');436 const input = document.createElement('input');
419 input.type = 'file';437 input.type = 'file';
420 input.accept = 'image/*';438 input.accept = 'image/*,video/*';
421 input.onchange = async (e) => {439 input.onchange = async (e) => {
422 const caption = await onSelectImage(e, prompt, quiet);440 const caption = await onSelectImage(e, prompt, quiet);
423 resolve(caption);441 resolve(caption);
@@ -427,6 +445,18 @@ async function captionCommandCallback(args, prompt) {
427 });445 });
428}446}
429447
448/**
449 * Checks if video captioning is available for the current source.
450 * @returns {boolean} True if video captioning is supported for the current source.
451 */
452function isVideoCaptioningAvailable() {
453 if (extension_settings.caption.source !== 'multimodal') {
454 return false;
455 }
456
457 return ['google', 'vertexai'].includes(extension_settings.caption.multimodal_api);
458}
459
430jQuery(async function () {460jQuery(async function () {
431 function addSendPictureButton() {461 function addSendPictureButton() {
432 const sendButton = $(`462 const sendButton = $(`
@@ -515,13 +545,17 @@ jQuery(async function () {
515 });545 });
516 }546 }
517 function addPictureSendForm() {547 function addPictureSendForm() {
518 const inputHtml = '<input id="img_file" type="file" hidden accept="image/*">';548 const imgInput = document.createElement('input');
549 imgInput.type = 'file';
550 imgInput.id = 'img_file';
551 imgInput.accept = 'image/*,video/*';
552 imgInput.hidden = true;
553 imgInput.addEventListener('change', (e) => onSelectImage(e, '', false));
519 const imgForm = document.createElement('form');554 const imgForm = document.createElement('form');
520 imgForm.id = 'img_form';555 imgForm.id = 'img_form';
521 $(imgForm).append(inputHtml);556 imgForm.appendChild(imgInput);
522 $(imgForm).hide();557 imgForm.hidden = true;
523 $('#form_sheld').append(imgForm);558 $('#form_sheld').append(imgForm);
524 $('#img_file').on('change', (e) => onSelectImage(e.originalEvent, '', false));
525 }559 }
526 async function switchMultimodalBlocks() {560 async function switchMultimodalBlocks() {
527 await addRemoteEndpointModels();561 await addRemoteEndpointModels();
@@ -668,13 +702,33 @@ jQuery(async function () {
668 saveSettingsDebounced();702 saveSettingsDebounced();
669 });703 });
670704
671 const onMessageEvent = async (messageId) => {705 const onMessageEvent = async (/** @type {number} */ messageId) => {
672 if (!extension_settings.caption.auto_mode) {706 if (!extension_settings.caption.auto_mode) {
673 return;707 return;
674 }708 }
675709
676 const message = getContext().chat[messageId];710 const message = getContext().chat[messageId];
677 await captionExistingMessage(message, 0);711 if (Array.isArray(message?.extra?.media) && message.extra.media.length > 0) {
712 for (let mediaIndex = 0; mediaIndex < message.extra.media.length; mediaIndex++) {
713 const mediaAttachment = message.extra.media[mediaIndex];
714 if (mediaAttachment.type === MEDIA_TYPE.VIDEO && !isVideoCaptioningAvailable()) {
715 continue;
716 }
717 if (mediaAttachment.type === MEDIA_TYPE.AUDIO) {
718 continue;
719 }
720 // Skip already captioned images and non-uploaded (generated, etc.) images
721 if (mediaAttachment.source !== MEDIA_SOURCE.UPLOAD || mediaAttachment.captioned) {
722 continue;
723 }
724 try {
725 await captionExistingMessage(message, mediaIndex);
726 } catch (e) {
727 console.error(`Auto-captioning failed for message ID ${messageId}, media index ${mediaIndex}`, e);
728 continue;
729 }
730 }
731 }
678 };732 };
679733
680 eventSource.on(event_types.MESSAGE_SENT, onMessageEvent);734 eventSource.on(event_types.MESSAGE_SENT, onMessageEvent);
@@ -683,21 +737,22 @@ jQuery(async function () {
683 $(document).on('click', '.mes_img_caption', async function () {737 $(document).on('click', '.mes_img_caption', async function () {
684 const animationClass = 'fa-fade';738 const animationClass = 'fa-fade';
685 const messageBlock = $(this).closest('.mes');739 const messageBlock = $(this).closest('.mes');
686 const imageBlock = $(this).closest('.mes_img_container');740 const mediaContainer = $(this).closest('.mes_media_container');
687 const messageImg = imageBlock.find('.mes_img');741 const messageMedia = mediaContainer.find('.mes_img, .mes_video');
688 if (messageImg.hasClass(animationClass)) return;742 if (messageMedia.hasClass(animationClass)) return;
689 messageImg.addClass(animationClass);743 messageMedia.addClass(animationClass);
690 try {744 try {
691 const messageId = Number(messageBlock.attr('mesid'));745 const messageId = Number(messageBlock.attr('mesid'));
692 const imageIndex = Number(imageBlock.attr('data-index'));746 const mediaIndex = Number(mediaContainer.attr('data-index'));
693 const data = getContext().chat[messageId];747 const data = getContext().chat[messageId];
694 await captionExistingMessage(data, imageIndex);748 await captionExistingMessage(data, mediaIndex);
695 appendMediaToMessage(data, messageBlock, SCROLL_BEHAVIOR.KEEP);749 appendMediaToMessage(data, messageBlock, SCROLL_BEHAVIOR.KEEP);
696 await saveChatConditional();750 await saveChatConditional();
697 } catch (e) {751 } catch (e) {
698 console.error('Message image recaption failed', e);752 console.error('Message image recaption failed', e);
753 toastr.error(e.message || 'Unknown error', 'Failed to caption');
699 } finally {754 } finally {
700 messageImg.removeClass(animationClass);755 messageMedia.removeClass(animationClass);
701 }756 }
702 });757 });
703758
public/scripts/extensions/shared.js+4 -3
@@ -33,12 +33,13 @@ export async function getMultimodalCaption(base64Img, prompt) {
33 const base64Bytes = base64Img.length * 0.75;33 const base64Bytes = base64Img.length * 0.75;
34 const compressionLimit = 2 * 1024 * 1024;34 const compressionLimit = 2 * 1024 * 1024;
35 const safeMimeTypes = ['image/jpeg', 'image/png', 'image/webp'];35 const safeMimeTypes = ['image/jpeg', 'image/png', 'image/webp'];
36 const mimeType = base64Img?.split(';')?.[0]?.split(':')?.[1];36 const mimeType = base64Img?.split(';')?.[0]?.split(':')?.[1] || 'image/jpeg';
37 const isImage = mimeType.startsWith('image/');
37 const thumbnailNeeded = ['google', 'openrouter', 'mistral', 'groq', 'vertexai'].includes(extension_settings.caption.multimodal_api);38 const thumbnailNeeded = ['google', 'openrouter', 'mistral', 'groq', 'vertexai'].includes(extension_settings.caption.multimodal_api);
38 if ((thumbnailNeeded && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {39 if ((isImage && thumbnailNeeded && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {
39 const maxSide = 2048;40 const maxSide = 2048;
40 base64Img = await createThumbnail(base64Img, maxSide, maxSide);41 base64Img = await createThumbnail(base64Img, maxSide, maxSide);
41 } else if (!safeMimeTypes.includes(mimeType)) {42 } else if (isImage && !safeMimeTypes.includes(mimeType)) {
42 base64Img = await createThumbnail(base64Img, null, null);43 base64Img = await createThumbnail(base64Img, null, null);
43 }44 }
44 if (isOllama && base64Img.startsWith('data:image/')) {45 if (isOllama && base64Img.startsWith('data:image/')) {
public/scripts/extensions/stable-diffusion/index.js+3 -1
@@ -52,7 +52,7 @@ import {
52 SlashCommandArgument,52 SlashCommandArgument,
53 SlashCommandNamedArgument,53 SlashCommandNamedArgument,
54} from '../../slash-commands/SlashCommandArgument.js';54} from '../../slash-commands/SlashCommandArgument.js';
55import { debounce_timeout, MEDIA_DISPLAY, MEDIA_TYPE, SCROLL_BEHAVIOR, VIDEO_EXTENSIONS } from '../../constants.js';55import { debounce_timeout, MEDIA_DISPLAY, MEDIA_SOURCE, MEDIA_TYPE, SCROLL_BEHAVIOR, VIDEO_EXTENSIONS } from '../../constants.js';
56import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';56import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
57import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';57import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
58import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';58import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
@@ -4123,6 +4123,7 @@ async function sendMessage(prompt, image, generationType, additionalNegativePref
4123 title: prompt,4123 title: prompt,
4124 generation_type: generationType,4124 generation_type: generationType,
4125 negative: additionalNegativePrefix,4125 negative: additionalNegativePrefix,
4126 source: MEDIA_SOURCE.GENERATED,
4126 };4127 };
4127 /** @type {ChatMessage} */4128 /** @type {ChatMessage} */
4128 const message = {4129 const message = {
@@ -4386,6 +4387,7 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete,
4386 const result = {4387 const result = {
4387 url: '',4388 url: '',
4388 type: MEDIA_TYPE.IMAGE,4389 type: MEDIA_TYPE.IMAGE,
4390 source: MEDIA_SOURCE.GENERATED,
4389 };4391 };
43904392
4391 try {4393 try {