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: {