diff --git a/default/config.yaml b/default/config.yaml index fd0be655c..edee81009 100644 --- a/default/config.yaml +++ b/default/config.yaml @@ -54,6 +54,10 @@ extras: openai: # Will send a random user ID to OpenAI completion API randomizeUserId: false + # If not empty, will add this as a system message to the start of every caption completion prompt + # Example: "Perform the instructions to the best of your ability.\n\n" (for LLaVA) + # Not used in image inlining mode + captionSystemPrompt: "" # -- DEEPL TRANSLATION CONFIGURATION -- deepl: # Available options: default, more, less, prefer_more, prefer_less diff --git a/public/script.js b/public/script.js index da11e20dd..21e8208c0 100644 --- a/public/script.js +++ b/public/script.js @@ -291,6 +291,7 @@ export const event_types = { MESSAGE_DELETED: 'message_deleted', IMPERSONATE_READY: 'impersonate_ready', CHAT_CHANGED: 'chat_id_changed', + GENERATION_STARTED: 'generation_started', GENERATION_STOPPED: 'generation_stopped', EXTENSIONS_FIRST_LOAD: 'extensions_first_load', SETTINGS_LOADED: 'settings_loaded', @@ -2925,6 +2926,7 @@ export async function generateRaw(prompt, api, instructOverride) { // Returns a promise that resolves when the text is done generating. async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops } = {}, dryRun = false) { console.log('Generate entered'); + eventSource.emit(event_types.GENERATION_STARTED, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops }, dryRun); setGenerationProgress(0); generation_started = new Date(); diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js index 9c5d9aa1a..a096ba8ba 100644 --- a/public/scripts/RossAscends-mods.js +++ b/public/scripts/RossAscends-mods.js @@ -18,6 +18,7 @@ import { eventSource, menu_type, substituteParams, + callPopup, } from '../script.js'; import { @@ -995,9 +996,31 @@ export function initRossMods() { console.debug('Accepting edits with Ctrl+Enter'); editMesDone.trigger('click'); } else if (is_send_press == false) { - console.debug('Regenerating with Ctrl+Enter'); - $('#option_regenerate').click(); - $('#options').hide(); + const skipConfirmKey = 'RegenerateWithCtrlEnter'; + const skipConfirm = LoadLocalBool(skipConfirmKey); + function doRegenerate() { + console.debug('Regenerating with Ctrl+Enter'); + $('#option_regenerate').trigger('click'); + $('#options').hide(); + } + if (skipConfirm) { + doRegenerate(); + } else { + const popupText = ` +
Are you sure you want to regenerate the latest message?
+ `; + callPopup(popupText, 'confirm').then(result =>{ + if (!result) { + return; + } + const regenerateWithCtrlEnter = $('#regenerateWithCtrlEnter').prop('checked'); + SaveLocal(skipConfirmKey, regenerateWithCtrlEnter); + doRegenerate(); + }); + } } else { console.debug('Ctrl+Enter ignored'); } diff --git a/public/scripts/bookmarks.js b/public/scripts/bookmarks.js index 131f95961..7b59fcab4 100644 --- a/public/scripts/bookmarks.js +++ b/public/scripts/bookmarks.js @@ -237,6 +237,12 @@ async function convertSoloToGroupChat() { return; } + const confirm = await callPopup('Are you sure you want to convert this chat to a group chat?', 'confirm'); + + if (!confirm) { + return; + } + const character = characters[this_chid]; // Populate group required fields diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js index aa666a232..97cc4cf37 100644 --- a/public/scripts/extensions/caption/index.js +++ b/public/scripts/extensions/caption/index.js @@ -300,7 +300,7 @@ jQuery(function () { $('#caption_prompt_block').toggle(isMultimodal); $('#caption_multimodal_api').val(extension_settings.caption.multimodal_api); $('#caption_multimodal_model').val(extension_settings.caption.multimodal_model); - $('#caption_multimodal_model option').each(function () { + $('#caption_multimodal_block [data-type]').each(function () { const type = $(this).data('type'); $(this).toggle(type === extension_settings.caption.multimodal_api); }); @@ -351,6 +351,10 @@ jQuery(function () { +
@@ -377,6 +381,7 @@ jQuery(function () { switchMultimodalBlocks(); $('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode)); + $('#caption_allow_reverse_proxy').prop('checked', !!(extension_settings.caption.allow_reverse_proxy)); $('#caption_source').val(extension_settings.caption.source); $('#caption_prompt').val(extension_settings.caption.prompt); $('#caption_template').val(extension_settings.caption.template); @@ -394,4 +399,8 @@ jQuery(function () { extension_settings.caption.template = String($('#caption_template').val()); saveSettingsDebounced(); }); + $('#caption_allow_reverse_proxy').on('input', () => { + extension_settings.caption.allow_reverse_proxy = $('#caption_allow_reverse_proxy').prop('checked'); + saveSettingsDebounced(); + }); }); diff --git a/public/scripts/extensions/shared.js b/public/scripts/extensions/shared.js index 9058204ec..7d4e16720 100644 --- a/public/scripts/extensions/shared.js +++ b/public/scripts/extensions/shared.js @@ -1,7 +1,8 @@ import { getRequestHeaders } from '../../script.js'; import { extension_settings } from '../extensions.js'; +import { oai_settings } from '../openai.js'; import { SECRET_KEYS, secret_state } from '../secrets.js'; -import { createThumbnail } from '../utils.js'; +import { createThumbnail, isValidUrl } from '../utils.js'; /** * Generates a caption for an image using a multimodal model. @@ -35,6 +36,15 @@ export async function getMultimodalCaption(base64Img, prompt) { } } + const useReverseProxy = + extension_settings.caption.multimodal_api === 'openai' + && extension_settings.caption.allow_reverse_proxy + && oai_settings.reverse_proxy + && isValidUrl(oai_settings.reverse_proxy); + + const proxyUrl = useReverseProxy ? oai_settings.reverse_proxy : ''; + const proxyPassword = useReverseProxy ? oai_settings.proxy_password : ''; + const apiResult = await fetch(`/api/${isGoogle ? 'google' : 'openai'}/caption-image`, { method: 'POST', headers: getRequestHeaders(), @@ -46,6 +56,8 @@ export async function getMultimodalCaption(base64Img, prompt) { : { api: extension_settings.caption.multimodal_api || 'openai', model: extension_settings.caption.multimodal_model || 'gpt-4-vision-preview', + reverse_proxy: proxyUrl, + proxy_password: proxyPassword, }), }), }); diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 7108a9914..bfd49f7c8 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -1108,7 +1108,7 @@ function printGroupCandidates() { showNavigator: true, showSizeChanger: true, pageSize: Number(localStorage.getItem(storageKey)) || 5, - sizeChangerOptions: [5, 10, 25, 50, 100, 200], + sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], afterSizeSelectorChange: function (e) { localStorage.setItem(storageKey, e.target.value); }, @@ -1135,7 +1135,7 @@ function printGroupMembers() { showNavigator: true, showSizeChanger: true, pageSize: Number(localStorage.getItem(storageKey)) || 5, - sizeChangerOptions: [5, 10, 25, 50, 100, 200], + sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], afterSizeSelectorChange: function (e) { localStorage.setItem(storageKey, e.target.value); }, diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index 8b88f97a1..3a7a48907 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -323,7 +323,7 @@ class PresetManager { } async deleteCurrentPreset() { - const { preset_names } = this.getPresetList(); + const { preset_names, presets } = this.getPresetList(); const value = this.getSelectedPreset(); const nameToDelete = this.getSelectedPresetName(); @@ -335,7 +335,9 @@ class PresetManager { $(this.select).find(`option[value="${value}"]`).remove(); if (this.isKeyedApi()) { - preset_names.splice(preset_names.indexOf(value), 1); + const index = preset_names.indexOf(nameToDelete); + preset_names.splice(index, 1); + presets.splice(index, 1); } else { delete preset_names[nameToDelete]; } diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 1768762db..23ca73a56 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -842,6 +842,36 @@ async function unhideMessageCallback(_, arg) { return ''; } +/** + * Copium for running group actions when the member is offscreen. + * @param {number} chid - character ID + * @param {string} action - one of 'enable', 'disable', 'up', 'down', 'peek', 'remove' + * @returns {void} + */ +function performGroupMemberAction(chid, action) { + const memberSelector = `.group_member[chid="${chid}"]`; + // Do not optimize. Paginator gets recreated on every action + const paginationSelector = '#rm_group_members_pagination'; + const pageSizeSelector = '#rm_group_members_pagination select'; + let wasOffscreen = false; + let paginationValue = null; + let pageValue = null; + + if ($(memberSelector).length === 0) { + wasOffscreen = true; + paginationValue = Number($(pageSizeSelector).val()); + pageValue = $(paginationSelector).pagination('getCurrentPageNum'); + $(pageSizeSelector).val($(pageSizeSelector).find('option').last().val()).trigger('change'); + } + + $(memberSelector).find(`[data-action="${action}"]`).trigger('click'); + + if (wasOffscreen) { + $(pageSizeSelector).val(paginationValue).trigger('change'); + $(paginationSelector).pagination('go', pageValue); + } +} + async function disableGroupMemberCallback(_, arg) { if (!selected_group) { toastr.warning('Cannot run /disable command outside of a group chat.'); @@ -855,7 +885,7 @@ async function disableGroupMemberCallback(_, arg) { return ''; } - $(`.group_member[chid="${chid}"] [data-action="disable"]`).trigger('click'); + performGroupMemberAction(chid, 'disable'); return ''; } @@ -872,7 +902,7 @@ async function enableGroupMemberCallback(_, arg) { return ''; } - $(`.group_member[chid="${chid}"] [data-action="enable"]`).trigger('click'); + performGroupMemberAction(chid, 'enable'); return ''; } @@ -889,7 +919,7 @@ async function moveGroupMemberUpCallback(_, arg) { return ''; } - $(`.group_member[chid="${chid}"] [data-action="up"]`).trigger('click'); + performGroupMemberAction(chid, 'up'); return ''; } @@ -906,7 +936,7 @@ async function moveGroupMemberDownCallback(_, arg) { return ''; } - $(`.group_member[chid="${chid}"] [data-action="down"]`).trigger('click'); + performGroupMemberAction(chid, 'down'); return ''; } @@ -928,7 +958,7 @@ async function peekCallback(_, arg) { return ''; } - $(`.group_member[chid="${chid}"] [data-action="view"]`).trigger('click'); + performGroupMemberAction(chid, 'peek'); return ''; } @@ -950,7 +980,7 @@ async function removeGroupMemberCallback(_, arg) { return ''; } - $(`.group_member[chid="${chid}"] [data-action="remove"]`).trigger('click'); + performGroupMemberAction(chid, 'remove'); return ''; } diff --git a/src/endpoints/openai.js b/src/endpoints/openai.js index 23a19f943..cb98cf274 100644 --- a/src/endpoints/openai.js +++ b/src/endpoints/openai.js @@ -4,6 +4,7 @@ const express = require('express'); const FormData = require('form-data'); const fs = require('fs'); const { jsonParser, urlencodedParser } = require('../express-common'); +const { getConfigValue } = require('../util'); const router = express.Router(); @@ -11,15 +12,19 @@ router.post('/caption-image', jsonParser, async (request, response) => { try { let key = ''; - if (request.body.api === 'openai') { + if (request.body.api === 'openai' && !request.body.reverse_proxy) { key = readSecret(SECRET_KEYS.OPENAI); } - if (request.body.api === 'openrouter') { + if (request.body.api === 'openrouter' && !request.body.reverse_proxy) { key = readSecret(SECRET_KEYS.OPENROUTER); } - if (!key) { + if (request.body.reverse_proxy && request.body.proxy_password) { + key = request.body.proxy_password; + } + + if (!key && !request.body.reverse_proxy) { console.log('No key found for API', request.body.api); return response.sendStatus(400); } @@ -38,6 +43,14 @@ router.post('/caption-image', jsonParser, async (request, response) => { max_tokens: 500, }; + const captionSystemPrompt = getConfigValue('openai.captionSystemPrompt'); + if (captionSystemPrompt) { + body.messages.unshift({ + role: 'system', + content: captionSystemPrompt, + }); + } + console.log('Multimodal captioning request', body); let apiUrl = ''; @@ -52,6 +65,10 @@ router.post('/caption-image', jsonParser, async (request, response) => { apiUrl = 'https://api.openai.com/v1/chat/completions'; } + if (request.body.reverse_proxy) { + apiUrl = `${request.body.reverse_proxy}/chat/completions`; + } + const result = await fetch(apiUrl, { method: 'POST', headers: {