Add automatic image captioning mode
| @@ -408,6 +408,7 @@ export const event_types = { | ||
| 408 | 408 | MESSAGE_EDITED: 'message_edited', |
| 409 | 409 | MESSAGE_DELETED: 'message_deleted', |
| 410 | 410 | MESSAGE_UPDATED: 'message_updated', |
| 411 | + MESSAGE_FILE_EMBEDDED: 'message_file_embedded', | |
| 411 | 412 | IMPERSONATE_READY: 'impersonate_ready', |
| 412 | 413 | CHAT_CHANGED: 'chat_id_changed', |
| 413 | 414 | GENERATION_STARTED: 'generation_started', |
| @@ -3404,6 +3405,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 3404 | 3405 | let regexedMessage = getRegexedString(message, regexType, options); |
| 3405 | 3406 | regexedMessage = await appendFileContent(chatItem, regexedMessage); |
| 3406 | 3407 | |
| 3408 | + if (chatItem?.extra?.append_title && chatItem?.extra?.title) { | |
| 3409 | + regexedMessage = `${regexedMessage}\n\n${chatItem.extra.title}`; | |
| 3410 | + } | |
| 3411 | + | |
| 3407 | 3412 | return { |
| 3408 | 3413 | ...chatItem, |
| 3409 | 3414 | mes: regexedMessage, |
| @@ -417,6 +417,7 @@ function embedMessageFile(messageId, messageBlock) { | ||
| 417 | 417 | } |
| 418 | 418 | |
| 419 | 419 | await populateFileAttachment(message, 'embed_file_input'); |
| 420 | + await eventSource.emit(event_types.MESSAGE_FILE_EMBEDDED, messageId); | |
| 420 | 421 | appendMediaToMessage(message, messageBlock); |
| 421 | 422 | await saveChatConditional(); |
| 422 | 423 | } |
| @@ -614,6 +615,8 @@ async function deleteMessageImage() { | ||
| 614 | 615 | const message = chat[mesId]; |
| 615 | 616 | delete message.extra.image; |
| 616 | 617 | delete message.extra.inline_image; |
| 618 | + delete message.extra.title; | |
| 619 | + delete message.extra.append_title; | |
| 617 | 620 | mesBlock.find('.mes_img_container').removeClass('img_extra'); |
| 618 | 621 | mesBlock.find('.mes_img').attr('src', ''); |
| 619 | 622 | await saveChatConditional(); |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; |
| 2 | 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | 3 | import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; |
| 4 | 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; |
| 5 | 5 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 6 | 6 | import { getMultimodalCaption } from '../shared.js'; |
| @@ -84,12 +84,11 @@ async function setSpinnerIcon() { | ||
| 84 | 84 | } |
| 85 | 85 | |
| 86 | 86 | /** |
| 87 | 87 | * SendsWraps a captionedcaption messagewith toa themessage chattemplate. |
| 88 | 88 | * @param {string} caption CaptionRaw textcaption |
| 89 | 89 | * @paramreturns {Promise<string>} image ImageWrapped URLcaption |
| 90 | 90 | */ |
| 91 | 91 | async function sendCaptionedMessagewrapCaptionTemplate(caption, image) { |
| 92 | - const context = getContext(); | |
| 93 | 92 | let template = extension_settings.caption.template || TEMPLATE_DEFAULT; |
| 94 | 93 | |
| 95 | 94 | if (!/{{caption}}/i.test(template)) { |
| @@ -101,7 +100,7 @@ async function sendCaptionedMessage(caption, image) { | ||
| 101 | 100 | |
| 102 | 101 | if (extension_settings.caption.refine_mode) { |
| 103 | 102 | messageText = await callPopup( |
| 104 | 103 | '<h3>Review and edit the generated messagecaption:</h3>Press "Cancel" to abort the caption sending.', |
| 105 | 104 | 'input', |
| 106 | 105 | messageText, |
| 107 | 106 | { rows: 5, okButton: 'Send' }); |
| @@ -111,6 +110,55 @@ async function sendCaptionedMessage(caption, image) { | ||
| 111 | 110 | } |
| 112 | 111 | } |
| 113 | 112 | |
| 113 | + return messageText; | |
| 114 | +} | |
| 115 | + | |
| 116 | +/** | |
| 117 | + * Appends caption to an existing message. | |
| 118 | + * @param {Object} data Message data | |
| 119 | + * @returns {Promise<void>} | |
| 120 | + */ | |
| 121 | +async function captionExistingMessage(data) { | |
| 122 | + if (!(data?.extra?.image)) { | |
| 123 | + return; | |
| 124 | + } | |
| 125 | + | |
| 126 | + const imageData = await fetch(data.extra.image); | |
| 127 | + const blob = await imageData.blob(); | |
| 128 | + const type = imageData.headers.get('Content-Type'); | |
| 129 | + const file = new File([blob], 'image.png', { type }); | |
| 130 | + const caption = await getCaptionForFile(file, null, true); | |
| 131 | + | |
| 132 | + if (!caption) { | |
| 133 | + console.warn('Failed to generate a caption for the image.'); | |
| 134 | + return; | |
| 135 | + } | |
| 136 | + | |
| 137 | + const wrappedCaption = await wrapCaptionTemplate(caption); | |
| 138 | + | |
| 139 | + const messageText = String(data.mes).trim(); | |
| 140 | + | |
| 141 | + if (!messageText) { | |
| 142 | + data.extra.inline_image = false; | |
| 143 | + data.mes = wrappedCaption; | |
| 144 | + data.extra.title = wrappedCaption; | |
| 145 | + } | |
| 146 | + else { | |
| 147 | + data.extra.inline_image = true; | |
| 148 | + data.extra.append_title = true; | |
| 149 | + data.extra.title = wrappedCaption; | |
| 150 | + } | |
| 151 | +} | |
| 152 | + | |
| 153 | +/** | |
| 154 | + * Sends a captioned message to the chat. | |
| 155 | + * @param {string} caption Caption text | |
| 156 | + * @param {string} image Image URL | |
| 157 | + */ | |
| 158 | +async function sendCaptionedMessage(caption, image) { | |
| 159 | + const messageText = await wrapCaptionTemplate(caption); | |
| 160 | + | |
| 161 | + const context = getContext(); | |
| 114 | 162 | const message = { |
| 115 | 163 | name: context.name1, |
| 116 | 164 | is_user: true, |
| @@ -423,6 +471,7 @@ jQuery(async function () { | ||
| 423 | 471 | $('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode)); |
| 424 | 472 | $('#caption_allow_reverse_proxy').prop('checked', !!(extension_settings.caption.allow_reverse_proxy)); |
| 425 | 473 | $('#caption_prompt_ask').prop('checked', !!(extension_settings.caption.prompt_ask)); |
| 474 | + $('#caption_auto_mode').prop('checked', !!(extension_settings.caption.auto_mode)); | |
| 426 | 475 | $('#caption_source').val(extension_settings.caption.source); |
| 427 | 476 | $('#caption_prompt').val(extension_settings.caption.prompt); |
| 428 | 477 | $('#caption_template').val(extension_settings.caption.template); |
| @@ -448,6 +497,22 @@ jQuery(async function () { | ||
| 448 | 497 | extension_settings.caption.prompt_ask = $('#caption_prompt_ask').prop('checked'); |
| 449 | 498 | saveSettingsDebounced(); |
| 450 | 499 | }); |
| 500 | + $('#caption_auto_mode').on('input', () => { | |
| 501 | + extension_settings.caption.auto_mode = !!$('#caption_auto_mode').prop('checked'); | |
| 502 | + saveSettingsDebounced(); | |
| 503 | + }); | |
| 504 | + | |
| 505 | + const onMessageEvent = async (index) => { | |
| 506 | + if (!extension_settings.caption.auto_mode) { | |
| 507 | + return; | |
| 508 | + } | |
| 509 | + | |
| 510 | + const data = getContext().chat[index]; | |
| 511 | + await captionExistingMessage(data); | |
| 512 | + }; | |
| 513 | + | |
| 514 | + eventSource.on(event_types.MESSAGE_SENT, onMessageEvent); | |
| 515 | + eventSource.on(event_types.MESSAGE_FILE_EMBEDDED, onMessageEvent); | |
| 451 | 516 | |
| 452 | 517 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'caption', |
| 453 | 518 | callback: captionCommandCallback, |
| @@ -92,6 +92,11 @@ | ||
| 92 | 92 | </div> |
| 93 | 93 | <label for="caption_template"><span data-i18n="Message Template">Message Template</span> <small><span data-i18n="(use _space">(use </span> <code>{{caption}}</code> <span data-i18n="macro)">macro)</span></small></label> |
| 94 | 94 | <textarea id="caption_template" class="text_pole" rows="2" placeholder="< Use default >">{{TEMPLATE_DEFAULT}}</textarea> |
| 95 | + <label class="checkbox_label" for="caption_auto_mode"> | |
| 96 | + <input id="caption_auto_mode" type="checkbox" class="checkbox"> | |
| 97 | + <span data-i18n="Automatically caption images">Automatically caption images</span> | |
| 98 | + <i class="fa-solid fa-info-circle" title="Automatically caption images when they are pasted into the chat or attached to messages."></i> | |
| 99 | + </label> | |
| 95 | 100 | <label class="checkbox_label margin-bot-10px" for="caption_refine_mode"> |
| 96 | 101 | <input id="caption_refine_mode" type="checkbox" class="checkbox"> |
| 97 | 102 | <span data-i18n="Edit captions before saving">Edit captions before saving</span> |