mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into ffmpeg-videobg
This commit is contained in:
@@ -409,6 +409,7 @@ function RA_autoconnect(PrevApi) {
|
||||
|| (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
|
||||
|| (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
|
||||
|| (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
|
||||
|| (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)
|
||||
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
|
||||
) {
|
||||
$('#api_button_openai').trigger('click');
|
||||
@@ -1047,7 +1048,7 @@ export function initRossMods() {
|
||||
//Enter to send when send_textarea in focus
|
||||
if (document.activeElement == hotkeyTargets['send_textarea']) {
|
||||
const sendOnEnter = shouldSendOnEnter();
|
||||
if (!event.shiftKey && !event.ctrlKey && !event.altKey && event.key == 'Enter' && sendOnEnter) {
|
||||
if (!event.isComposing && !event.shiftKey && !event.ctrlKey && !event.altKey && event.key == 'Enter' && sendOnEnter) {
|
||||
event.preventDefault();
|
||||
sendTextareaMessage();
|
||||
return;
|
||||
@@ -1119,7 +1120,7 @@ export function initRossMods() {
|
||||
const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
|
||||
customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
|
||||
onClose: (popup) => {
|
||||
regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false;
|
||||
regenerateWithCtrlEnter = Boolean(popup.inputResults.get('regenerateWithCtrlEnter') ?? false);
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
|
@@ -6,6 +6,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { flashHighlight, stringFormat } from './utils.js';
|
||||
import { t } from './i18n.js';
|
||||
import { Popup } from './popup.js';
|
||||
|
||||
const BG_METADATA_KEY = 'custom_background';
|
||||
const LIST_METADATA_KEY = 'chat_backgrounds';
|
||||
@@ -291,7 +292,7 @@ async function onDeleteBackgroundClick(e) {
|
||||
const bgToDelete = $(this).closest('.bg_example');
|
||||
const url = bgToDelete.data('url');
|
||||
const isCustom = bgToDelete.attr('custom') === 'true';
|
||||
const confirm = await callPopup('<h3>Delete the background?</h3>', 'confirm');
|
||||
const confirm = await Popup.show.confirm(t`Delete the background?`, null);
|
||||
const bg = bgToDelete.attr('bgfile');
|
||||
|
||||
if (confirm) {
|
||||
|
@@ -33,6 +33,12 @@
|
||||
* @property {number} role - The specific function or purpose of the extension.
|
||||
* @property {boolean} vectorized - Indicates if the extension is optimized for vectorized processing.
|
||||
* @property {number} display_index - The order in which the extension should be displayed for user interfaces.
|
||||
* @property {boolean} match_persona_description - Wether to match against the persona description.
|
||||
* @property {boolean} match_character_description - Wether to match against the persona description.
|
||||
* @property {boolean} match_character_personality - Wether to match against the character personality.
|
||||
* @property {boolean} match_character_depth_prompt - Wether to match against the character depth prompt.
|
||||
* @property {boolean} match_scenario - Wether to match against the character scenario.
|
||||
* @property {boolean} match_creator_notes - Wether to match against the character creator notes.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@@ -74,6 +74,11 @@ const hash_derivations = {
|
||||
'b6835114b7303ddd78919a82e4d9f7d8c26ed0d7dfc36beeb12d524f6144eab1':
|
||||
'DeepSeek-V2.5'
|
||||
,
|
||||
|
||||
// THUDM-GLM 4
|
||||
'854b703e44ca06bdb196cc471c728d15dbab61e744fe6cdce980086b61646ed1':
|
||||
'GLM-4'
|
||||
,
|
||||
};
|
||||
|
||||
const substr_derivations = {
|
||||
|
@@ -43,10 +43,12 @@ import EventSourceStream from './sse-stream.js';
|
||||
* @property {boolean?} [stream=false] - Whether to stream the response
|
||||
* @property {ChatCompletionMessage[]} messages - Array of chat messages
|
||||
* @property {string} [model] - Optional model name to use for completion
|
||||
* @property {string} chat_completion_source - Source provider for chat completion
|
||||
* @property {string} chat_completion_source - Source provider
|
||||
* @property {number} max_tokens - Maximum number of tokens to generate
|
||||
* @property {number} [temperature] - Optional temperature parameter for response randomness
|
||||
* @property {string} [custom_url] - Optional custom URL for chat completion
|
||||
* @property {string} [custom_url] - Optional custom URL
|
||||
* @property {string} [reverse_proxy] - Optional reverse proxy URL
|
||||
* @property {string} [proxy_password] - Optional proxy password
|
||||
*/
|
||||
|
||||
/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */
|
||||
@@ -80,7 +82,6 @@ export class TextCompletionService {
|
||||
*/
|
||||
static createRequestData({ stream = false, prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) {
|
||||
const payload = {
|
||||
...props,
|
||||
stream,
|
||||
prompt,
|
||||
max_tokens,
|
||||
@@ -90,6 +91,7 @@ export class TextCompletionService {
|
||||
api_server: api_server ?? getTextGenServer(api_type),
|
||||
temperature,
|
||||
min_p,
|
||||
...props,
|
||||
};
|
||||
|
||||
// Remove undefined values to avoid API errors
|
||||
@@ -387,9 +389,8 @@ export class ChatCompletionService {
|
||||
* @param {ChatCompletionPayload} custom
|
||||
* @returns {ChatCompletionPayload}
|
||||
*/
|
||||
static createRequestData({ stream = false, messages, model, chat_completion_source, max_tokens, temperature, custom_url, ...props }) {
|
||||
static createRequestData({ stream = false, messages, model, chat_completion_source, max_tokens, temperature, custom_url, reverse_proxy, proxy_password, ...props }) {
|
||||
const payload = {
|
||||
...props,
|
||||
stream,
|
||||
messages,
|
||||
model,
|
||||
@@ -397,6 +398,11 @@ export class ChatCompletionService {
|
||||
max_tokens,
|
||||
temperature,
|
||||
custom_url,
|
||||
reverse_proxy,
|
||||
proxy_password,
|
||||
use_makersuite_sysprompt: true,
|
||||
claude_use_sysprompt: true,
|
||||
...props,
|
||||
};
|
||||
|
||||
// Remove undefined values to avoid API errors
|
||||
|
@@ -661,6 +661,7 @@ function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal,
|
||||
let deleteButton = isExternal ? `<button class="btn_delete menu_button" data-name="${externalId}" data-i18n="[title]Delete" title="Delete"><i class="fa-fw fa-solid fa-trash-can"></i></button>` : '';
|
||||
let updateButton = isExternal ? `<button class="btn_update menu_button displayNone" data-name="${externalId}" title="Update available"><i class="fa-solid fa-download fa-fw"></i></button>` : '';
|
||||
let moveButton = isExternal && isUserAdmin ? `<button class="btn_move menu_button" data-name="${externalId}" data-i18n="[title]Move" title="Move"><i class="fa-solid fa-folder-tree fa-fw"></i></button>` : '';
|
||||
let branchButton = isExternal && isUserAdmin ? `<button class="btn_branch menu_button" data-name="${externalId}" data-i18n="[title]Switch branch" title="Switch branch"><i class="fa-solid fa-code-branch fa-fw"></i></button>` : '';
|
||||
let modulesInfo = '';
|
||||
|
||||
if (isActive && Array.isArray(manifest.optional)) {
|
||||
@@ -701,6 +702,7 @@ function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal,
|
||||
|
||||
<div class="extension_actions flex-container alignItemsCenter">
|
||||
${updateButton}
|
||||
${branchButton}
|
||||
${moveButton}
|
||||
${deleteButton}
|
||||
</div>
|
||||
@@ -944,6 +946,44 @@ async function onDeleteClick() {
|
||||
}
|
||||
}
|
||||
|
||||
async function onBranchClick() {
|
||||
const extensionName = $(this).data('name');
|
||||
const isCurrentUserAdmin = isAdmin();
|
||||
const isGlobal = getExtensionType(extensionName) === 'global';
|
||||
if (isGlobal && !isCurrentUserAdmin) {
|
||||
toastr.error(t`You don't have permission to switch branch.`);
|
||||
return;
|
||||
}
|
||||
|
||||
let newBranch = '';
|
||||
|
||||
const branches = await getExtensionBranches(extensionName, isGlobal);
|
||||
const selectElement = document.createElement('select');
|
||||
selectElement.classList.add('text_pole', 'wide100p');
|
||||
selectElement.addEventListener('change', function () {
|
||||
newBranch = this.value;
|
||||
});
|
||||
for (const branch of branches) {
|
||||
const option = document.createElement('option');
|
||||
option.value = branch.name;
|
||||
option.textContent = `${branch.name} (${branch.commit}) [${branch.label}]`;
|
||||
option.selected = branch.current;
|
||||
selectElement.appendChild(option);
|
||||
}
|
||||
|
||||
const popup = new Popup(selectElement, POPUP_TYPE.CONFIRM, '', {
|
||||
okButton: t`Switch`,
|
||||
cancelButton: t`Cancel`,
|
||||
});
|
||||
const popupResult = await popup.show();
|
||||
|
||||
if (!popupResult || !newBranch) {
|
||||
return;
|
||||
}
|
||||
|
||||
await switchExtensionBranch(extensionName, isGlobal, newBranch);
|
||||
}
|
||||
|
||||
async function onMoveClick() {
|
||||
const extensionName = $(this).data('name');
|
||||
const isCurrentUserAdmin = isAdmin();
|
||||
@@ -1055,13 +1095,83 @@ async function getExtensionVersion(extensionName, abortSignal) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of branches for a specific extension.
|
||||
* @param {string} extensionName The name of the extension
|
||||
* @param {boolean} isGlobal Whether the extension is global or not
|
||||
* @returns {Promise<ExtensionBranch[]>} List of branches for the extension
|
||||
* @typedef {object} ExtensionBranch
|
||||
* @property {string} name The name of the branch
|
||||
* @property {string} commit The commit hash of the branch
|
||||
* @property {boolean} current Whether this branch is the current one
|
||||
* @property {string} label The commit label of the branch
|
||||
*/
|
||||
async function getExtensionBranches(extensionName, isGlobal) {
|
||||
try {
|
||||
const response = await fetch('/api/extensions/branches', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
extensionName,
|
||||
global: isGlobal,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
toastr.error(text || response.statusText, t`Extension branches fetch failed`);
|
||||
console.error('Extension branches fetch failed', response.status, response.statusText, text);
|
||||
return [];
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the branch of an extension.
|
||||
* @param {string} extensionName The name of the extension
|
||||
* @param {boolean} isGlobal If the extension is global
|
||||
* @param {string} branch Branch name to switch to
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function switchExtensionBranch(extensionName, isGlobal, branch) {
|
||||
try {
|
||||
const response = await fetch('/api/extensions/switch', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
extensionName,
|
||||
branch,
|
||||
global: isGlobal,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
toastr.error(text || response.statusText, t`Extension branch switch failed`);
|
||||
console.error('Extension branch switch failed', response.status, response.statusText, text);
|
||||
return;
|
||||
}
|
||||
|
||||
toastr.success(t`Extension ${extensionName} switched to ${branch}`);
|
||||
await loadExtensionSettings({}, false, false);
|
||||
void showExtensionsDetails();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a third-party extension via the API.
|
||||
* @param {string} url Extension repository URL
|
||||
* @param {boolean} global Is the extension global?
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function installExtension(url, global) {
|
||||
export async function installExtension(url, global, branch = '') {
|
||||
console.debug('Extension installation started', url);
|
||||
|
||||
toastr.info(t`Please wait...`, t`Installing extension`);
|
||||
@@ -1072,6 +1182,7 @@ export async function installExtension(url, global) {
|
||||
body: JSON.stringify({
|
||||
url,
|
||||
global,
|
||||
branch,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1406,9 +1517,17 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
|
||||
await popup.complete(POPUP_RESULT.AFFIRMATIVE);
|
||||
},
|
||||
};
|
||||
/** @type {import('./popup.js').CustomPopupInput} */
|
||||
const branchNameInput = {
|
||||
id: 'extension_branch_name',
|
||||
label: t`Branch or tag name (optional)`,
|
||||
type: 'text',
|
||||
tooltip: 'e.g. main, dev, v1.0.0',
|
||||
};
|
||||
|
||||
const customButtons = isCurrentUserAdmin ? [installForAllButton] : [];
|
||||
const popup = new Popup(html, POPUP_TYPE.INPUT, suggestUrl ?? '', { okButton, customButtons });
|
||||
const customInputs = [branchNameInput];
|
||||
const popup = new Popup(html, POPUP_TYPE.INPUT, suggestUrl ?? '', { okButton, customButtons, customInputs });
|
||||
const input = await popup.show();
|
||||
|
||||
if (!input) {
|
||||
@@ -1417,7 +1536,8 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
|
||||
}
|
||||
|
||||
const url = String(input).trim();
|
||||
await installExtension(url, global);
|
||||
const branchName = String(popup.inputResults.get('extension_branch_name') ?? '').trim();
|
||||
await installExtension(url, global, branchName);
|
||||
}
|
||||
|
||||
export async function initExtensions() {
|
||||
@@ -1433,6 +1553,7 @@ export async function initExtensions() {
|
||||
$(document).on('click', '.extensions_info .extension_block .btn_update', onUpdateClick);
|
||||
$(document).on('click', '.extensions_info .extension_block .btn_delete', onDeleteClick);
|
||||
$(document).on('click', '.extensions_info .extension_block .btn_move', onMoveClick);
|
||||
$(document).on('click', '.extensions_info .extension_block .btn_branch', onBranchClick);
|
||||
|
||||
/**
|
||||
* Handles the click event for the third-party extension import button.
|
||||
|
@@ -291,7 +291,7 @@ async function installAsset(url, assetType, filename) {
|
||||
try {
|
||||
if (category === 'extension') {
|
||||
console.debug(DEBUG_PREFIX, 'Installing extension ', url);
|
||||
await installExtension(url);
|
||||
await installExtension(url, false);
|
||||
console.debug(DEBUG_PREFIX, 'Extension installed.');
|
||||
return;
|
||||
}
|
||||
@@ -309,7 +309,7 @@ async function installAsset(url, assetType, filename) {
|
||||
console.debug(DEBUG_PREFIX, 'Importing character ', filename);
|
||||
const blob = await result.blob();
|
||||
const file = new File([blob], filename, { type: blob.type });
|
||||
await processDroppedFiles([file], true);
|
||||
await processDroppedFiles([file]);
|
||||
console.debug(DEBUG_PREFIX, 'Character downloaded.');
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ To install a single 3rd party extension, use the "Install Extensions"
|
||||
<span data-i18n="Characters">Characters</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer-content" id="assets_menu">
|
||||
<div id="assets_menu">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -428,6 +428,7 @@ jQuery(async function () {
|
||||
'zerooneai': SECRET_KEYS.ZEROONEAI,
|
||||
'groq': SECRET_KEYS.GROQ,
|
||||
'cohere': SECRET_KEYS.COHERE,
|
||||
'xai': SECRET_KEYS.XAI,
|
||||
};
|
||||
|
||||
if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
|
||||
|
@@ -31,6 +31,7 @@
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>
|
||||
<option value="vllm">vLLM</option>
|
||||
<option value="xai">xAI (Grok)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex1 flex-container flexFlowColumn flexNoGap">
|
||||
@@ -46,13 +47,24 @@
|
||||
<option data-type="mistral" value="mistral-small-2503">mistral-small-2503</option>
|
||||
<option data-type="mistral" value="mistral-small-latest">mistral-small-latest</option>
|
||||
<option data-type="zerooneai" value="yi-vision">yi-vision</option>
|
||||
<option data-type="openai" value="gpt-4.1">gpt-4.1</option>
|
||||
<option data-type="openai" value="gpt-4.1-2025-04-14">gpt-4.1-2025-04-14</option>
|
||||
<option data-type="openai" value="gpt-4.1-mini">gpt-4.1-mini</option>
|
||||
<option data-type="openai" value="gpt-4.1-mini-2025-04-14">gpt-4.1-mini-2025-04-14</option>
|
||||
<option data-type="openai" value="gpt-4.1-nano">gpt-4.1-nano</option>
|
||||
<option data-type="openai" value="gpt-4.1-nano-2025-04-14">gpt-4.1-nano-2025-04-14</option>
|
||||
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
|
||||
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
|
||||
<option data-type="openai" value="gpt-4o">gpt-4o</option>
|
||||
<option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option>
|
||||
<option data-type="openai" value="gpt-4o-mini-2024-07-18">gpt-4o-mini-2024-07-18</option>
|
||||
<option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option>
|
||||
<option data-type="openai" value="o1">o1</option>
|
||||
<option data-type="openai" value="o1-2024-12-17">o1-2024-12-17</option>
|
||||
<option data-type="openai" value="o3">o3</option>
|
||||
<option data-type="openai" value="o3-2025-04-16">o3-2025-04-16</option>
|
||||
<option data-type="openai" value="o4-mini">o4-mini</option>
|
||||
<option data-type="openai" value="o4-mini-2025-04-16">o4-mini-2025-04-16</option>
|
||||
<option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option>
|
||||
<option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>
|
||||
<option data-type="anthropic" value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option>
|
||||
@@ -67,33 +79,33 @@
|
||||
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
|
||||
<option data-type="google" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
|
||||
<option data-type="google" value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option>
|
||||
<option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp</option>
|
||||
<option data-type="google" value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-lite-preview-02-05">gemini-2.0-flash-lite-preview-02-05</option>
|
||||
<option data-type="google" value="gemini-2.0-flash">gemini-2.0-flash</option>
|
||||
<option data-type="google" value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
|
||||
<option data-type="google" value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05 → 2.5-pro-exp-03-25</option>
|
||||
<option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp → 2.5-pro-exp-03-25</option>
|
||||
<option data-type="google" value="gemini-exp-1206">gemini-exp-1206 → 2.5-pro-exp-03-25</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-exp-image-generation">gemini-2.0-flash-exp-image-generation</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-thinking-exp">gemini-2.0-flash-thinking-exp</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-thinking-exp-01-21">gemini-2.0-flash-thinking-exp-01-21</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-thinking-exp-1219">gemini-2.0-flash-thinking-exp-1219</option>
|
||||
<option data-type="google" value="gemini-1.5-flash">gemini-1.5-flash</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-001">gemini-1.5-flash-001</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-002">gemini-1.5-flash-002</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-exp-0827">gemini-1.5-flash-exp-0827</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option>
|
||||
<option data-type="google" value="gemini-exp-1114">gemini-exp-1114</option>
|
||||
<option data-type="google" value="gemini-exp-1121">gemini-exp-1121</option>
|
||||
<option data-type="google" value="gemini-exp-1206">gemini-exp-1206</option>
|
||||
<option data-type="google" value="gemini-1.5-pro">gemini-1.5-pro</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option>
|
||||
<option data-type="google" value="gemini-2.0-flash">gemini-2.0-flash</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-thinking-exp-01-21">gemini-2.0-flash-thinking-exp-01-21 → 2.5-flash-preview-04-17</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-thinking-exp-1219">gemini-2.0-flash-thinking-exp-1219 → 2.5-flash-preview-04-17</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-thinking-exp">gemini-2.0-flash-thinking-exp → 2.5-flash-preview-04-17</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-lite-preview-02-05">gemini-2.0-flash-lite-preview-02-05</option>
|
||||
<option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>
|
||||
<option data-type="google" value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option>
|
||||
<option data-type="google" value="gemini-1.5-pro-001">gemini-1.5-pro-001</option>
|
||||
<option data-type="google" value="gemini-1.5-pro-002">gemini-1.5-pro-002</option>
|
||||
<option data-type="google" value="gemini-1.5-pro-exp-0801">gemini-1.5-pro-exp-0801</option>
|
||||
<option data-type="google" value="gemini-1.5-pro-exp-0827">gemini-1.5-pro-exp-0827</option>
|
||||
<option data-type="google" value="gemini-1.5-pro-001">gemini-1.5-pro-001</option>
|
||||
<option data-type="google" value="gemini-1.5-pro">gemini-1.5-pro</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-002">gemini-1.5-flash-002</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-001">gemini-1.5-flash-001</option>
|
||||
<option data-type="google" value="gemini-1.5-flash">gemini-1.5-flash</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-8b-001">gemini-1.5-flash-8b-001</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-8b-exp-0924">gemini-1.5-flash-8b-exp-0924</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option>
|
||||
<option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>
|
||||
<option data-type="google" value="learnlm-1.5-pro-experimental">learnlm-1.5-pro-experimental</option>
|
||||
<option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option>
|
||||
<option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option>
|
||||
<option data-type="groq" value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option>
|
||||
@@ -134,6 +146,8 @@
|
||||
<option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
|
||||
<option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>
|
||||
<option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
|
||||
<option data-type="xai" value="grok-2-vision-1212">grok-2-vision-1212</option>
|
||||
<option data-type="xai" value="grok-vision-beta">grok-vision-beta</option>
|
||||
</select>
|
||||
</div>
|
||||
<div data-type="ollama">
|
||||
|
@@ -132,7 +132,7 @@
|
||||
</label>
|
||||
<label class="flex-container alignItemsCenter" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
|
||||
<input type="radio" name="memory_position" value="1" />
|
||||
<span data-i18n="In-chat @ Depth">In-chat @ Depth</span> <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="999" />
|
||||
<span data-i18n="In-chat @ Depth">In-chat @ Depth</span> <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="9999" />
|
||||
<span data-i18n="as">as</span>
|
||||
<select id="memory_role" class="text_pole widthNatural">
|
||||
<option value="0" data-i18n="System">System</option>
|
||||
|
@@ -113,14 +113,14 @@
|
||||
<span data-i18n="Min Depth">Min Depth</span>
|
||||
<span class="fa-solid fa-circle-question note-link-span"></span>
|
||||
</small>
|
||||
<input name="min_depth" class="text_pole textarea_compact" type="number" min="-1" max="999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
|
||||
<input name="min_depth" class="text_pole textarea_compact" type="number" min="-1" max="9999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
|
||||
</div>
|
||||
<div class="flex1 flex-container flexNoGap">
|
||||
<small data-i18n="[title]ext_regex_max_depth_desc" title="When applied to prompts or display, only affect messages no more than N levels deep. 0 = last message, 1 = penultimate message, etc. System prompt and utility prompts are not affected. Max must be greater than Min for regex to apply.">
|
||||
<span data-i18n="Max Depth">Max Depth</span>
|
||||
<span class="fa-solid fa-circle-question note-link-span"></span>
|
||||
</small>
|
||||
<input name="max_depth" class="text_pole textarea_compact" type="number" min="0" max="999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
|
||||
<input name="max_depth" class="text_pole textarea_compact" type="number" min="0" max="9999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js';
|
||||
import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';
|
||||
import { t } from '../i18n.js';
|
||||
import { oai_settings } from '../openai.js';
|
||||
import { oai_settings, proxies } from '../openai.js';
|
||||
import { SECRET_KEYS, secret_state } from '../secrets.js';
|
||||
import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js';
|
||||
import { getTokenCountAsync } from '../tokenizers.js';
|
||||
@@ -153,6 +153,10 @@ function throwIfInvalidModel(useReverseProxy) {
|
||||
throw new Error('Cohere API key is not set.');
|
||||
}
|
||||
|
||||
if (extension_settings.caption.multimodal_api === 'xai' && !secret_state[SECRET_KEYS.XAI]) {
|
||||
throw new Error('xAI API key is not set.');
|
||||
}
|
||||
|
||||
if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {
|
||||
throw new Error('Ollama server URL is not set.');
|
||||
}
|
||||
@@ -306,9 +310,10 @@ export class ConnectionManagerRequestService {
|
||||
* @param {boolean?} [custom.includePreset=true]
|
||||
* @param {boolean?} [custom.includeInstruct=true]
|
||||
* @param {Partial<InstructSettings>?} [custom.instructSettings] Override instruct settings
|
||||
* @param {Record<string, any>} [overridePayload] - Override payload for the request
|
||||
* @returns {Promise<import('../custom-request.js').ExtractedData | (() => AsyncGenerator<import('../custom-request.js').StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
|
||||
*/
|
||||
static async sendRequest(profileId, prompt, maxTokens, custom = this.defaultSendRequestParams) {
|
||||
static async sendRequest(profileId, prompt, maxTokens, custom = this.defaultSendRequestParams, overridePayload = {}) {
|
||||
const { stream, signal, extractData, includePreset, includeInstruct, instructSettings } = { ...this.defaultSendRequestParams, ...custom };
|
||||
|
||||
const context = SillyTavern.getContext();
|
||||
@@ -326,6 +331,8 @@ export class ConnectionManagerRequestService {
|
||||
throw new Error(`API type ${selectedApiMap.selected} does not support chat completions`);
|
||||
}
|
||||
|
||||
const proxyPreset = proxies.find((p) => p.name === profile.proxy);
|
||||
|
||||
const messages = Array.isArray(prompt) ? prompt : [{ role: 'user', content: prompt }];
|
||||
return await context.ChatCompletionService.processRequest({
|
||||
stream,
|
||||
@@ -334,6 +341,9 @@ export class ConnectionManagerRequestService {
|
||||
model: profile.model,
|
||||
chat_completion_source: selectedApiMap.source,
|
||||
custom_url: profile['api-url'],
|
||||
reverse_proxy: proxyPreset?.url,
|
||||
proxy_password: proxyPreset?.password,
|
||||
...overridePayload,
|
||||
}, {
|
||||
presetName: includePreset ? profile.preset : undefined,
|
||||
}, extractData, signal);
|
||||
@@ -350,6 +360,7 @@ export class ConnectionManagerRequestService {
|
||||
model: profile.model,
|
||||
api_type: selectedApiMap.type,
|
||||
api_server: profile['api-url'],
|
||||
...overridePayload,
|
||||
}, {
|
||||
instructName: includeInstruct ? profile.instruct : undefined,
|
||||
presetName: includePreset ? profile.preset : undefined,
|
||||
|
@@ -81,6 +81,7 @@ const sources = {
|
||||
nanogpt: 'nanogpt',
|
||||
bfl: 'bfl',
|
||||
falai: 'falai',
|
||||
xai: 'xai',
|
||||
};
|
||||
|
||||
const initiators = {
|
||||
@@ -1303,6 +1304,7 @@ async function onModelChange() {
|
||||
sources.nanogpt,
|
||||
sources.bfl,
|
||||
sources.falai,
|
||||
sources.xai,
|
||||
];
|
||||
|
||||
if (cloudSources.includes(extension_settings.sd.source)) {
|
||||
@@ -1518,6 +1520,9 @@ async function loadSamplers() {
|
||||
case sources.bfl:
|
||||
samplers = ['N/A'];
|
||||
break;
|
||||
case sources.xai:
|
||||
samplers = ['N/A'];
|
||||
break;
|
||||
}
|
||||
|
||||
for (const sampler of samplers) {
|
||||
@@ -1708,6 +1713,9 @@ async function loadModels() {
|
||||
case sources.falai:
|
||||
models = await loadFalaiModels();
|
||||
break;
|
||||
case sources.xai:
|
||||
models = await loadXAIModels();
|
||||
break;
|
||||
}
|
||||
|
||||
for (const model of models) {
|
||||
@@ -1760,6 +1768,12 @@ async function loadFalaiModels() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function loadXAIModels() {
|
||||
return [
|
||||
{ value: 'grok-2-image-1212', text: 'grok-2-image-1212' },
|
||||
];
|
||||
}
|
||||
|
||||
async function loadPollinationsModels() {
|
||||
const result = await fetch('/api/sd/pollinations/models', {
|
||||
method: 'POST',
|
||||
@@ -2081,6 +2095,9 @@ async function loadSchedulers() {
|
||||
case sources.falai:
|
||||
schedulers = ['N/A'];
|
||||
break;
|
||||
case sources.xai:
|
||||
schedulers = ['N/A'];
|
||||
break;
|
||||
}
|
||||
|
||||
for (const scheduler of schedulers) {
|
||||
@@ -2166,6 +2183,12 @@ async function loadVaes() {
|
||||
case sources.bfl:
|
||||
vaes = ['N/A'];
|
||||
break;
|
||||
case sources.falai:
|
||||
vaes = ['N/A'];
|
||||
break;
|
||||
case sources.xai:
|
||||
vaes = ['N/A'];
|
||||
break;
|
||||
}
|
||||
|
||||
for (const vae of vaes) {
|
||||
@@ -2735,6 +2758,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
|
||||
case sources.falai:
|
||||
result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal);
|
||||
break;
|
||||
case sources.xai:
|
||||
result = await generateXAIImage(prefixedPrompt, negativePrompt, signal);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
@@ -3463,6 +3489,33 @@ async function generateBflImage(prompt, signal) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image using the xAI API.
|
||||
* @param {string} prompt The main instruction used to guide the image generation.
|
||||
* @param {string} _negativePrompt Negative prompt is not used in this API
|
||||
* @param {AbortSignal} signal An AbortSignal object that can be used to cancel the request.
|
||||
* @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateXAIImage(prompt, _negativePrompt, signal) {
|
||||
const result = await fetch('/api/sd/xai/generate', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
signal: signal,
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
model: extension_settings.sd.model,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
return { format: 'jpg', data: data.image };
|
||||
} else {
|
||||
const text = await result.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image using the FAL.AI API.
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
@@ -3782,6 +3835,8 @@ function isValidState() {
|
||||
return secret_state[SECRET_KEYS.BFL];
|
||||
case sources.falai:
|
||||
return secret_state[SECRET_KEYS.FALAI];
|
||||
case sources.xai:
|
||||
return secret_state[SECRET_KEYS.XAI];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -52,6 +52,7 @@
|
||||
<option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
|
||||
<option value="horde">Stable Horde</option>
|
||||
<option value="togetherai">TogetherAI</option>
|
||||
<option value="xai">xAI (Grok)</option>
|
||||
</select>
|
||||
<div data-sd-source="auto">
|
||||
<label for="sd_auto_url">SD Web UI URL</label>
|
||||
|
@@ -76,7 +76,7 @@
|
||||
<div id="tts_voicemap_block">
|
||||
</div>
|
||||
<hr>
|
||||
<form id="tts_provider_settings" class="inline-drawer-content">
|
||||
<form id="tts_provider_settings">
|
||||
</form>
|
||||
<div class="tts_buttons">
|
||||
<input id="tts_voices" class="menu_button" type="submit" value="Available voices" />
|
||||
|
@@ -79,6 +79,10 @@ class SystemTtsProvider {
|
||||
// Config //
|
||||
//########//
|
||||
|
||||
// Static constants for the simulated default voice
|
||||
static BROWSER_DEFAULT_VOICE_ID = '__browser_default__';
|
||||
static BROWSER_DEFAULT_VOICE_NAME = 'System Default Voice';
|
||||
|
||||
settings;
|
||||
ready = false;
|
||||
voices = [];
|
||||
@@ -168,51 +172,97 @@ class SystemTtsProvider {
|
||||
//#################//
|
||||
fetchTtsVoiceObjects() {
|
||||
if (!('speechSynthesis' in window)) {
|
||||
return [];
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const voices = speechSynthesis
|
||||
.getVoices()
|
||||
.sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))
|
||||
.map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));
|
||||
let voices = speechSynthesis.getVoices();
|
||||
|
||||
resolve(voices);
|
||||
}, 1);
|
||||
if (voices.length === 0) {
|
||||
// Edge compat: Provide default when voices empty
|
||||
console.warn('SystemTTS: getVoices() returned empty list. Providing browser default option.');
|
||||
const defaultVoice = {
|
||||
name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
|
||||
voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
|
||||
preview_url: false,
|
||||
lang: navigator.language || 'en-US',
|
||||
};
|
||||
resolve([defaultVoice]);
|
||||
} else {
|
||||
const mappedVoices = voices
|
||||
.sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))
|
||||
.map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));
|
||||
resolve(mappedVoices);
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
}
|
||||
|
||||
previewTtsVoice(voiceId) {
|
||||
if (!('speechSynthesis' in window)) {
|
||||
throw 'Speech synthesis API is not supported';
|
||||
throw new Error('Speech synthesis API is not supported');
|
||||
}
|
||||
|
||||
const voice = speechSynthesis.getVoices().find(x => x.voiceURI === voiceId);
|
||||
let voice = null;
|
||||
if (voiceId !== SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID) {
|
||||
const voices = speechSynthesis.getVoices();
|
||||
voice = voices.find(x => x.voiceURI === voiceId);
|
||||
|
||||
if (!voice) {
|
||||
throw `TTS Voice id ${voiceId} not found`;
|
||||
if (!voice && voices.length > 0) {
|
||||
console.warn(`SystemTTS Preview: Voice ID "${voiceId}" not found among available voices. Using browser default.`);
|
||||
} else if (!voice && voices.length === 0) {
|
||||
console.warn('SystemTTS Preview: Voice list is empty. Using browser default.');
|
||||
}
|
||||
} else {
|
||||
console.log('SystemTTS Preview: Using browser default voice as requested.');
|
||||
}
|
||||
|
||||
speechSynthesis.cancel();
|
||||
const text = getPreviewString(voice.lang);
|
||||
const langForPreview = voice ? voice.lang : (navigator.language || 'en-US');
|
||||
const text = getPreviewString(langForPreview);
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.voice = voice;
|
||||
|
||||
if (voice) {
|
||||
utterance.voice = voice;
|
||||
}
|
||||
|
||||
utterance.rate = this.settings.rate || 1;
|
||||
utterance.pitch = this.settings.pitch || 1;
|
||||
|
||||
utterance.onerror = (event) => {
|
||||
console.error(`SystemTTS Preview Error: ${event.error}`, event);
|
||||
};
|
||||
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
|
||||
async getVoice(voiceName) {
|
||||
if (!('speechSynthesis' in window)) {
|
||||
return { voice_id: null };
|
||||
return { voice_id: null, name: 'API Not Supported' };
|
||||
}
|
||||
|
||||
if (voiceName === SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME) {
|
||||
return {
|
||||
voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
|
||||
name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
const voices = speechSynthesis.getVoices();
|
||||
|
||||
if (voices.length === 0) {
|
||||
console.warn('SystemTTS: Empty voice list, using default fallback');
|
||||
return {
|
||||
voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
|
||||
name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
const match = voices.find(x => x.name == voiceName);
|
||||
|
||||
if (!match) {
|
||||
throw `TTS Voice name ${voiceName} not found`;
|
||||
throw new Error(`SystemTTS getVoice: TTS Voice name "${voiceName}" not found`);
|
||||
}
|
||||
|
||||
return { voice_id: match.voiceURI, name: match.name };
|
||||
@@ -237,7 +287,6 @@ class SystemTtsProvider {
|
||||
speechUtteranceChunker(utterance, {
|
||||
chunkLength: 200,
|
||||
}, function () {
|
||||
//some code to execute when done
|
||||
resolve(silence);
|
||||
console.log('System TTS done');
|
||||
});
|
||||
|
@@ -55,6 +55,8 @@ const getBatchSize = () => ['transformers', 'palm', 'ollama'].includes(settings.
|
||||
const settings = {
|
||||
// For both
|
||||
source: 'transformers',
|
||||
alt_endpoint_url: '',
|
||||
use_alt_endpoint: false,
|
||||
include_wi: false,
|
||||
togetherai_model: 'togethercomputer/m2-bert-80M-32k-retrieval',
|
||||
openai_model: 'text-embedding-ada-002',
|
||||
@@ -109,6 +111,7 @@ const settings = {
|
||||
const moduleWorker = new ModuleWorkerWrapper(synchronizeChat);
|
||||
const webllmProvider = new WebLlmVectorProvider();
|
||||
const cachedSummaries = new Map();
|
||||
const vectorApiRequiresUrl = ['llamacpp', 'vllm', 'ollama', 'koboldcpp'];
|
||||
|
||||
/**
|
||||
* Gets the Collection ID for a file embedded in the chat.
|
||||
@@ -777,14 +780,14 @@ function getVectorsRequestBody(args = {}) {
|
||||
break;
|
||||
case 'ollama':
|
||||
body.model = extension_settings.vectors.ollama_model;
|
||||
body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];
|
||||
body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];
|
||||
body.keep = !!extension_settings.vectors.ollama_keep;
|
||||
break;
|
||||
case 'llamacpp':
|
||||
body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];
|
||||
body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];
|
||||
break;
|
||||
case 'vllm':
|
||||
body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.VLLM];
|
||||
body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.VLLM];
|
||||
body.model = extension_settings.vectors.vllm_model;
|
||||
break;
|
||||
case 'webllm':
|
||||
@@ -826,11 +829,12 @@ async function getAdditionalArgs(items) {
|
||||
* @returns {Promise<number[]>} Saved hashes
|
||||
*/
|
||||
async function getSavedHashes(collectionId) {
|
||||
const args = await getAdditionalArgs([]);
|
||||
const response = await fetch('/api/vector/list', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
...getVectorsRequestBody(),
|
||||
...getVectorsRequestBody(args),
|
||||
collectionId: collectionId,
|
||||
source: settings.source,
|
||||
}),
|
||||
@@ -883,11 +887,18 @@ function throwIfSourceInvalid() {
|
||||
throw new Error('Vectors: API key missing', { cause: 'api_key_missing' });
|
||||
}
|
||||
|
||||
if (settings.source === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA] ||
|
||||
settings.source === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM] ||
|
||||
settings.source === 'koboldcpp' && !textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP] ||
|
||||
settings.source === 'llamacpp' && !textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) {
|
||||
throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' });
|
||||
if (vectorApiRequiresUrl.includes(settings.source) && settings.use_alt_endpoint) {
|
||||
if (!settings.alt_endpoint_url) {
|
||||
throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' });
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (settings.source === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA] ||
|
||||
settings.source === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM] ||
|
||||
settings.source === 'koboldcpp' && !textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP] ||
|
||||
settings.source === 'llamacpp' && !textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) {
|
||||
throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' });
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.source === 'ollama' && !settings.ollama_model || settings.source === 'vllm' && !settings.vllm_model) {
|
||||
@@ -1087,6 +1098,7 @@ function toggleSettings() {
|
||||
$('#webllm_vectorsModel').toggle(settings.source === 'webllm');
|
||||
$('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp');
|
||||
$('#google_vectorsModel').toggle(settings.source === 'palm');
|
||||
$('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source));
|
||||
if (settings.source === 'webllm') {
|
||||
loadWebLlmModels();
|
||||
}
|
||||
@@ -1144,6 +1156,9 @@ function loadWebLlmModels() {
|
||||
* @returns {Promise<Record<string, number[]>>} Calculated embeddings
|
||||
*/
|
||||
async function createWebLlmEmbeddings(items) {
|
||||
if (items.length === 0) {
|
||||
return /** @type {Record<string, number[]>} */ ({});
|
||||
}
|
||||
return executeWithWebLlmErrorHandling(async () => {
|
||||
const embeddings = await webllmProvider.embedTexts(items, settings.webllm_model);
|
||||
const result = /** @type {Record<string, number[]>} */ ({});
|
||||
@@ -1165,7 +1180,7 @@ async function createKoboldCppEmbeddings(items) {
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
items: items,
|
||||
server: textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP],
|
||||
server: settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP],
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1467,6 +1482,16 @@ jQuery(async () => {
|
||||
saveSettingsDebounced();
|
||||
toggleSettings();
|
||||
});
|
||||
$('#vector_altEndpointUrl_enabled').prop('checked', settings.use_alt_endpoint).on('input', () => {
|
||||
settings.use_alt_endpoint = $('#vector_altEndpointUrl_enabled').prop('checked');
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#vector_altEndpoint_address').val(settings.alt_endpoint_url).on('change', () => {
|
||||
settings.alt_endpoint_url = String($('#vector_altEndpoint_address').val());
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#api_key_nomicai').on('click', async () => {
|
||||
const popupText = 'NomicAI API Key:';
|
||||
const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', {
|
||||
|
@@ -25,6 +25,16 @@
|
||||
<option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn" id="vector_altEndpointUrl">
|
||||
<label class="checkbox_label" for="vector_altEndpointUrl_enabled" title="Enable secondary endpoint URL usage, instead of the main one.">
|
||||
<input id="vector_altEndpointUrl_enabled" type="checkbox" class="checkbox">
|
||||
<span data-i18n="Use secondary URL">Use secondary URL</span>
|
||||
</label>
|
||||
<label for="vector_altEndpoint_address" data-i18n="Secondary Embedding endpoint URL">
|
||||
Secondary Embedding endpoint URL
|
||||
</label>
|
||||
<input id="vector_altEndpoint_address" class="text_pole" type="text" placeholder="e.g. http://localhost:5001" />
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn" id="webllm_vectorsModel">
|
||||
<label for="vectors_webllm_model" data-i18n="Vectorization Model">
|
||||
Vectorization Model
|
||||
@@ -87,6 +97,7 @@
|
||||
Vectorization Model
|
||||
</label>
|
||||
<select id="vectors_cohere_model" class="text_pole">
|
||||
<option value="embed-v4.0">embed-v4.0</option>
|
||||
<option value="embed-english-v3.0">embed-english-v3.0</option>
|
||||
<option value="embed-multilingual-v3.0">embed-multilingual-v3.0</option>
|
||||
<option value="embed-english-light-v3.0">embed-english-light-v3.0</option>
|
||||
@@ -310,7 +321,7 @@
|
||||
<label for="vectors_file_depth_db" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
|
||||
<input type="radio" name="vectors_file_position_db" value="1" />
|
||||
<span data-i18n="In-chat @ Depth">In-chat @ Depth</span>
|
||||
<input id="vectors_file_depth_db" class="text_pole widthUnset" type="number" min="0" max="999" />
|
||||
<input id="vectors_file_depth_db" class="text_pole widthUnset" type="number" min="0" max="9999" />
|
||||
<span>as</span>
|
||||
<select id="vectors_file_depth_role_db" class="text_pole widthNatural">
|
||||
<option value="0" data-i18n="System">System</option>
|
||||
@@ -362,7 +373,7 @@
|
||||
<label for="vectors_depth" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
|
||||
<input type="radio" name="vectors_position" value="1" />
|
||||
<span data-i18n="In-chat @ Depth">In-chat @ Depth </span>
|
||||
<input id="vectors_depth" class="text_pole widthUnset" type="number" min="0" max="999" />
|
||||
<input id="vectors_depth" class="text_pole widthUnset" type="number" min="0" max="9999" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
|
@@ -13,6 +13,9 @@ import {
|
||||
getBase64Async,
|
||||
resetScrollHeight,
|
||||
initScrollHeight,
|
||||
localizePagination,
|
||||
renderPaginationDropdown,
|
||||
paginationDropdownChangeHandler,
|
||||
} from './utils.js';
|
||||
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from './RossAscends-mods.js';
|
||||
import { power_user, loadMovingUIState, sortEntitiesList } from './power-user.js';
|
||||
@@ -1374,6 +1377,8 @@ function getGroupCharacters({ doFilter, onlyMembers } = {}) {
|
||||
|
||||
function printGroupCandidates() {
|
||||
const storageKey = 'GroupCandidates_PerPage';
|
||||
const pageSize = Number(accountStorage.getItem(storageKey)) || 5;
|
||||
const sizeChangerOptions = [5, 10, 25, 50, 100, 200, 500, 1000];
|
||||
$('#rm_group_add_members_pagination').pagination({
|
||||
dataSource: getGroupCharacters({ doFilter: true, onlyMembers: false }),
|
||||
pageRange: 1,
|
||||
@@ -1382,18 +1387,20 @@ function printGroupCandidates() {
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
formatNavigator: PAGINATION_TEMPLATE,
|
||||
formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions),
|
||||
showNavigator: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: Number(accountStorage.getItem(storageKey)) || 5,
|
||||
sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000],
|
||||
afterSizeSelectorChange: function (e) {
|
||||
pageSize,
|
||||
afterSizeSelectorChange: function (e, size) {
|
||||
accountStorage.setItem(storageKey, e.target.value);
|
||||
paginationDropdownChangeHandler(e, size);
|
||||
},
|
||||
callback: function (data) {
|
||||
$('#rm_group_add_members').empty();
|
||||
for (const i of data) {
|
||||
$('#rm_group_add_members').append(getGroupCharacterBlock(i.item));
|
||||
}
|
||||
localizePagination($('#rm_group_add_members_pagination'));
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1401,6 +1408,9 @@ function printGroupCandidates() {
|
||||
function printGroupMembers() {
|
||||
const storageKey = 'GroupMembers_PerPage';
|
||||
$('.rm_group_members_pagination').each(function () {
|
||||
let that = this;
|
||||
const pageSize = Number(accountStorage.getItem(storageKey)) || 5;
|
||||
const sizeChangerOptions = [5, 10, 25, 50, 100, 200, 500, 1000];
|
||||
$(this).pagination({
|
||||
dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }),
|
||||
pageRange: 1,
|
||||
@@ -1411,16 +1421,18 @@ function printGroupMembers() {
|
||||
formatNavigator: PAGINATION_TEMPLATE,
|
||||
showNavigator: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: Number(accountStorage.getItem(storageKey)) || 5,
|
||||
sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000],
|
||||
afterSizeSelectorChange: function (e) {
|
||||
formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions),
|
||||
pageSize,
|
||||
afterSizeSelectorChange: function (e, size) {
|
||||
accountStorage.setItem(storageKey, e.target.value);
|
||||
paginationDropdownChangeHandler(e, size);
|
||||
},
|
||||
callback: function (data) {
|
||||
$('.rm_group_members').empty();
|
||||
for (const i of data) {
|
||||
$('.rm_group_members').append(getGroupCharacterBlock(i.item));
|
||||
}
|
||||
localizePagination($(that));
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1804,7 +1816,7 @@ async function createGroup() {
|
||||
const memberNames = characters.filter(x => members.includes(x.avatar)).map(x => x.name).join(', ');
|
||||
|
||||
if (!name) {
|
||||
name = `Group: ${memberNames}`;
|
||||
name = t`Group: ${memberNames}`;
|
||||
}
|
||||
|
||||
const avatar_url = $('#group_avatar_preview img').attr('src');
|
||||
|
@@ -394,7 +394,7 @@ function getHordeModelTemplate(option) {
|
||||
`));
|
||||
}
|
||||
|
||||
jQuery(function () {
|
||||
export function initHorde () {
|
||||
$('#horde_model').on('mousedown change', async function (e) {
|
||||
console.log('Horde model change', e);
|
||||
horde_settings.models = $('#horde_model').val();
|
||||
@@ -441,7 +441,7 @@ jQuery(function () {
|
||||
if (!isMobile()) {
|
||||
$('#horde_model').select2({
|
||||
width: '100%',
|
||||
placeholder: 'Select Horde models',
|
||||
placeholder: t`Select Horde models`,
|
||||
allowClear: true,
|
||||
closeOnSelect: false,
|
||||
templateSelection: function (data) {
|
||||
@@ -451,5 +451,5 @@ jQuery(function () {
|
||||
templateResult: getHordeModelTemplate,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -208,37 +208,39 @@ export function autoSelectInstructPreset(modelId) {
|
||||
|
||||
// Select matching instruct preset
|
||||
let foundMatch = false;
|
||||
for (const instruct_preset of instruct_presets) {
|
||||
// If instruct preset matches the context template
|
||||
if (power_user.instruct.bind_to_context && instruct_preset.name === power_user.context.preset) {
|
||||
foundMatch = true;
|
||||
selectInstructPreset(instruct_preset.name, { isAuto: true });
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If no match was found, auto-select instruct preset
|
||||
if (!foundMatch) {
|
||||
for (const preset of instruct_presets) {
|
||||
// If activation regex is set, check if it matches the model id
|
||||
if (preset.activation_regex) {
|
||||
try {
|
||||
const regex = regexFromString(preset.activation_regex);
|
||||
|
||||
// Stop on first match so it won't cycle back and forth between presets if multiple regexes match
|
||||
if (regex instanceof RegExp && regex.test(modelId)) {
|
||||
selectInstructPreset(preset.name, { isAuto: true });
|
||||
for (const preset of instruct_presets) {
|
||||
// If activation regex is set, check if it matches the model id
|
||||
if (preset.activation_regex) {
|
||||
try {
|
||||
const regex = regexFromString(preset.activation_regex);
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// If regex is invalid, ignore it
|
||||
console.warn(`Invalid instruct activation regex in preset "${preset.name}"`);
|
||||
// Stop on first match so it won't cycle back and forth between presets if multiple regexes match
|
||||
if (regex instanceof RegExp && regex.test(modelId)) {
|
||||
selectInstructPreset(preset.name, { isAuto: true });
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// If regex is invalid, ignore it
|
||||
console.warn(`Invalid instruct activation regex in preset "${preset.name}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
// If no match was found, auto-select instruct preset
|
||||
if (!foundMatch && power_user.instruct.bind_to_context) {
|
||||
for (const instruct_preset of instruct_presets) {
|
||||
// If instruct preset matches the context template
|
||||
if (instruct_preset.name === power_user.context.preset) {
|
||||
selectInstructPreset(instruct_preset.name, { isAuto: true });
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -15,12 +15,12 @@ import {
|
||||
extension_prompt_types,
|
||||
Generate,
|
||||
getExtensionPrompt,
|
||||
getExtensionPromptMaxDepth,
|
||||
getNextMessageId,
|
||||
getRequestHeaders,
|
||||
getStoppingStrings,
|
||||
is_send_press,
|
||||
main_api,
|
||||
MAX_INJECTION_DEPTH,
|
||||
name1,
|
||||
name2,
|
||||
replaceItemizedPromptText,
|
||||
@@ -184,6 +184,7 @@ export const chat_completion_sources = {
|
||||
ZEROONEAI: '01ai',
|
||||
NANOGPT: 'nanogpt',
|
||||
DEEPSEEK: 'deepseek',
|
||||
XAI: 'xai',
|
||||
};
|
||||
|
||||
const character_names_behavior = {
|
||||
@@ -215,6 +216,15 @@ const openrouter_middleout_types = {
|
||||
OFF: 'off',
|
||||
};
|
||||
|
||||
export const reasoning_effort_types = {
|
||||
auto: 'auto',
|
||||
low: 'low',
|
||||
medium: 'medium',
|
||||
high: 'high',
|
||||
min: 'min',
|
||||
max: 'max',
|
||||
};
|
||||
|
||||
const sensitiveFields = [
|
||||
'reverse_proxy',
|
||||
'proxy_password',
|
||||
@@ -257,6 +267,7 @@ export const settingsToUpdate = {
|
||||
nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false],
|
||||
deepseek_model: ['#model_deepseek_select', 'deepseek_model', false],
|
||||
zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false],
|
||||
xai_model: ['#model_xai_select', 'xai_model', false],
|
||||
custom_model: ['#custom_model_id', 'custom_model', false],
|
||||
custom_url: ['#custom_api_url_text', 'custom_url', false],
|
||||
custom_include_body: ['#custom_include_body', 'custom_include_body', false],
|
||||
@@ -345,6 +356,7 @@ const default_settings = {
|
||||
nanogpt_model: 'gpt-4o-mini',
|
||||
zerooneai_model: 'yi-large',
|
||||
deepseek_model: 'deepseek-chat',
|
||||
xai_model: 'grok-3-beta',
|
||||
custom_model: '',
|
||||
custom_url: '',
|
||||
custom_include_body: '',
|
||||
@@ -379,7 +391,7 @@ const default_settings = {
|
||||
continue_postfix: continue_postfix_types.SPACE,
|
||||
custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
|
||||
show_thoughts: true,
|
||||
reasoning_effort: 'medium',
|
||||
reasoning_effort: reasoning_effort_types.auto,
|
||||
enable_web_search: false,
|
||||
request_images: false,
|
||||
seed: -1,
|
||||
@@ -425,6 +437,7 @@ const oai_settings = {
|
||||
nanogpt_model: 'gpt-4o-mini',
|
||||
zerooneai_model: 'yi-large',
|
||||
deepseek_model: 'deepseek-chat',
|
||||
xai_model: 'grok-3-beta',
|
||||
custom_model: '',
|
||||
custom_url: '',
|
||||
custom_include_body: '',
|
||||
@@ -459,7 +472,7 @@ const oai_settings = {
|
||||
continue_postfix: continue_postfix_types.SPACE,
|
||||
custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
|
||||
show_thoughts: true,
|
||||
reasoning_effort: 'medium',
|
||||
reasoning_effort: reasoning_effort_types.auto,
|
||||
enable_web_search: false,
|
||||
request_images: false,
|
||||
seed: -1,
|
||||
@@ -738,7 +751,8 @@ async function populationInjectionPrompts(prompts, messages) {
|
||||
'assistant': extension_prompt_roles.ASSISTANT,
|
||||
};
|
||||
|
||||
for (let i = 0; i <= MAX_INJECTION_DEPTH; i++) {
|
||||
const maxDepth = getExtensionPromptMaxDepth();
|
||||
for (let i = 0; i <= maxDepth; i++) {
|
||||
// Get prompts for current depth
|
||||
const depthPrompts = prompts.filter(prompt => prompt.injection_depth === i && prompt.content);
|
||||
|
||||
@@ -1407,9 +1421,9 @@ export async function prepareOpenAIMessages({
|
||||
await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples });
|
||||
} catch (error) {
|
||||
if (error instanceof TokenBudgetExceededError) {
|
||||
toastr.error(t`An error occurred while counting tokens: Token budget exceeded.`);
|
||||
chatCompletion.log('Token budget exceeded.');
|
||||
promptManager.error = t`Not enough free tokens for mandatory prompts. Raise your token Limit or disable custom prompts.`;
|
||||
toastr.error(t`Mandatory prompts exceed the context size.`);
|
||||
chatCompletion.log('Mandatory prompts exceed the context size.');
|
||||
promptManager.error = t`Not enough free tokens for mandatory prompts. Raise your token limit or disable custom prompts.`;
|
||||
} else if (error instanceof InvalidCharacterNameError) {
|
||||
toastr.warning(t`An error occurred while counting tokens: Invalid character name`);
|
||||
chatCompletion.log('Invalid character name');
|
||||
@@ -1644,6 +1658,8 @@ export function getChatCompletionModel(source = null) {
|
||||
return oai_settings.nanogpt_model;
|
||||
case chat_completion_sources.DEEPSEEK:
|
||||
return oai_settings.deepseek_model;
|
||||
case chat_completion_sources.XAI:
|
||||
return oai_settings.xai_model;
|
||||
default:
|
||||
throw new Error(`Unknown chat completion source: ${activeSource}`);
|
||||
}
|
||||
@@ -1687,6 +1703,11 @@ function calculateOpenRouterCost() {
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.enable_web_search) {
|
||||
const webSearchCost = (0.02).toFixed(2);
|
||||
cost = t`${cost} + $${webSearchCost}`;
|
||||
}
|
||||
|
||||
$('#openrouter_max_prompt_cost').text(cost);
|
||||
}
|
||||
|
||||
@@ -1925,6 +1946,31 @@ async function sendAltScaleRequest(messages, logit_bias, signal, type) {
|
||||
return data.output;
|
||||
}
|
||||
|
||||
function getReasoningEffort() {
|
||||
// These sources expect the effort as string.
|
||||
const reasoningEffortSources = [
|
||||
chat_completion_sources.OPENAI,
|
||||
chat_completion_sources.CUSTOM,
|
||||
chat_completion_sources.XAI,
|
||||
chat_completion_sources.OPENROUTER,
|
||||
];
|
||||
|
||||
if (!reasoningEffortSources.includes(oai_settings.chat_completion_source)) {
|
||||
return oai_settings.reasoning_effort;
|
||||
}
|
||||
|
||||
switch (oai_settings.reasoning_effort) {
|
||||
case reasoning_effort_types.auto:
|
||||
return undefined;
|
||||
case reasoning_effort_types.min:
|
||||
return reasoning_effort_types.low;
|
||||
case reasoning_effort_types.max:
|
||||
return reasoning_effort_types.high;
|
||||
default:
|
||||
return oai_settings.reasoning_effort;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a chat completion request to backend
|
||||
* @param {string} type (impersonate, quiet, continue, etc)
|
||||
@@ -1961,13 +2007,14 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
|
||||
const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
|
||||
const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
|
||||
const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
|
||||
const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
|
||||
const isQuiet = type === 'quiet';
|
||||
const isImpersonate = type === 'impersonate';
|
||||
const isContinue = type === 'continue';
|
||||
const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isOAI && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_model));
|
||||
const useLogprobs = !!power_user.request_token_probabilities;
|
||||
const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom);
|
||||
const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom || isXAI);
|
||||
|
||||
// If we're using the window.ai extension, use that instead
|
||||
// Doesn't support logit bias yet
|
||||
@@ -2010,7 +2057,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
'char_name': name2,
|
||||
'group_names': getGroupNames(),
|
||||
'include_reasoning': Boolean(oai_settings.show_thoughts),
|
||||
'reasoning_effort': String(oai_settings.reasoning_effort),
|
||||
'reasoning_effort': getReasoningEffort(),
|
||||
'enable_web_search': Boolean(oai_settings.enable_web_search),
|
||||
'request_images': Boolean(oai_settings.request_images),
|
||||
'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing,
|
||||
@@ -2026,14 +2073,14 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
}
|
||||
|
||||
// Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite
|
||||
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK].includes(oai_settings.chat_completion_source)) {
|
||||
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {
|
||||
await validateReverseProxy();
|
||||
generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
|
||||
generate_data['proxy_password'] = oai_settings.proxy_password;
|
||||
}
|
||||
|
||||
// Add logprobs request (currently OpenAI only, max 5 on their side)
|
||||
if (useLogprobs && (isOAI || isCustom || isDeepSeek)) {
|
||||
if (useLogprobs && (isOAI || isCustom || isDeepSeek || isXAI)) {
|
||||
generate_data['logprobs'] = 5;
|
||||
}
|
||||
|
||||
@@ -2152,29 +2199,42 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
}
|
||||
}
|
||||
|
||||
if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
|
||||
if (isXAI) {
|
||||
if (generate_data.model.includes('grok-3-mini')) {
|
||||
delete generate_data.presence_penalty;
|
||||
delete generate_data.frequency_penalty;
|
||||
}
|
||||
if (generate_data.model.includes('grok-vision')) {
|
||||
delete generate_data.tools;
|
||||
delete generate_data.tool_choice;
|
||||
}
|
||||
}
|
||||
|
||||
if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano || isXAI) && oai_settings.seed >= 0) {
|
||||
generate_data['seed'] = oai_settings.seed;
|
||||
}
|
||||
|
||||
if (isOAI && (oai_settings.openai_model.startsWith('o1') || oai_settings.openai_model.startsWith('o3'))) {
|
||||
generate_data.messages.forEach((msg) => {
|
||||
if (msg.role === 'system') {
|
||||
msg.role = 'user';
|
||||
}
|
||||
});
|
||||
if (isOAI && /^(o1|o3|o4)/.test(oai_settings.openai_model)) {
|
||||
generate_data.max_completion_tokens = generate_data.max_tokens;
|
||||
delete generate_data.max_tokens;
|
||||
delete generate_data.logprobs;
|
||||
delete generate_data.top_logprobs;
|
||||
delete generate_data.n;
|
||||
delete generate_data.stop;
|
||||
delete generate_data.logit_bias;
|
||||
delete generate_data.temperature;
|
||||
delete generate_data.top_p;
|
||||
delete generate_data.frequency_penalty;
|
||||
delete generate_data.presence_penalty;
|
||||
delete generate_data.tools;
|
||||
delete generate_data.tool_choice;
|
||||
delete generate_data.stop;
|
||||
delete generate_data.logit_bias;
|
||||
if (oai_settings.openai_model.startsWith('o1')) {
|
||||
generate_data.messages.forEach((msg) => {
|
||||
if (msg.role === 'system') {
|
||||
msg.role = 'user';
|
||||
}
|
||||
});
|
||||
delete generate_data.n;
|
||||
delete generate_data.tools;
|
||||
delete generate_data.tool_choice;
|
||||
}
|
||||
}
|
||||
|
||||
await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);
|
||||
@@ -2210,7 +2270,8 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
|
||||
if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) {
|
||||
const swipeIndex = parsed.choices[0].index - 1;
|
||||
swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state);
|
||||
// FIXME: state.reasoning should be an array to support multi-swipe
|
||||
swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state, { overrideShowThoughts: false });
|
||||
} else {
|
||||
text += getStreamingReply(parsed, state);
|
||||
}
|
||||
@@ -2278,6 +2339,11 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov
|
||||
state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');
|
||||
}
|
||||
return data.choices?.[0]?.delta?.content || '';
|
||||
} else if (chat_completion_source === chat_completion_sources.XAI) {
|
||||
if (show_thoughts) {
|
||||
state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');
|
||||
}
|
||||
return data.choices?.[0]?.delta?.content || '';
|
||||
} else if (chat_completion_source === chat_completion_sources.OPENROUTER) {
|
||||
if (show_thoughts) {
|
||||
state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
|
||||
@@ -2310,6 +2376,7 @@ function parseChatCompletionLogprobs(data) {
|
||||
switch (oai_settings.chat_completion_source) {
|
||||
case chat_completion_sources.OPENAI:
|
||||
case chat_completion_sources.DEEPSEEK:
|
||||
case chat_completion_sources.XAI:
|
||||
case chat_completion_sources.CUSTOM:
|
||||
if (!data.choices?.length) {
|
||||
return null;
|
||||
@@ -3231,6 +3298,7 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;
|
||||
oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;
|
||||
oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;
|
||||
oai_settings.xai_model = settings.xai_model ?? default_settings.xai_model;
|
||||
oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
|
||||
oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
|
||||
oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;
|
||||
@@ -3316,6 +3384,8 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#model_deepseek_select').val(oai_settings.deepseek_model);
|
||||
$(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true);
|
||||
$('#model_01ai_select').val(oai_settings.zerooneai_model);
|
||||
$('#model_xai_select').val(oai_settings.xai_model);
|
||||
$(`#model_xai_select option[value="${oai_settings.xai_model}"`).attr('selected', true);
|
||||
$('#custom_model_id').val(oai_settings.custom_model);
|
||||
$('#custom_api_url_text').val(oai_settings.custom_url);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context);
|
||||
@@ -3512,7 +3582,7 @@ async function getStatusOpen() {
|
||||
chat_completion_source: oai_settings.chat_completion_source,
|
||||
};
|
||||
|
||||
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK].includes(oai_settings.chat_completion_source)) {
|
||||
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {
|
||||
await validateReverseProxy();
|
||||
}
|
||||
|
||||
@@ -3781,7 +3851,7 @@ function createLogitBiasListItem(entry) {
|
||||
}
|
||||
|
||||
async function createNewLogitBiasPreset() {
|
||||
const name = await callPopup('Preset name:', 'input');
|
||||
const name = await Popup.show.input(t`Preset name:`, null);
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
@@ -4092,9 +4162,15 @@ function getMaxContextOpenAI(value) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
return unlocked_max;
|
||||
}
|
||||
else if (value.startsWith('o1') || value.startsWith('o3')) {
|
||||
else if (value.includes('gpt-4.1')) {
|
||||
return max_1mil;
|
||||
}
|
||||
else if (value.startsWith('o1')) {
|
||||
return max_128k;
|
||||
}
|
||||
else if (value.startsWith('o4') || value.startsWith('o3')) {
|
||||
return max_200k;
|
||||
}
|
||||
else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {
|
||||
return max_128k;
|
||||
}
|
||||
@@ -4165,6 +4241,80 @@ function getMaxContextWindowAI(value) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum context size for the Mistral model
|
||||
* @param {string} model Model identifier
|
||||
* @param {boolean} isUnlocked Whether context limits are unlocked
|
||||
* @returns {number} Maximum context size in tokens
|
||||
*/
|
||||
function getMistralMaxContext(model, isUnlocked) {
|
||||
if (isUnlocked) {
|
||||
return unlocked_max;
|
||||
}
|
||||
|
||||
if (Array.isArray(model_list) && model_list.length > 0) {
|
||||
const contextLength = model_list.find((record) => record.id === model)?.max_context_length;
|
||||
if (contextLength) {
|
||||
return contextLength;
|
||||
}
|
||||
}
|
||||
|
||||
const contextMap = {
|
||||
'codestral-2411-rc5': 262144,
|
||||
'codestral-2412': 262144,
|
||||
'codestral-2501': 262144,
|
||||
'codestral-latest': 262144,
|
||||
'codestral-mamba-2407': 262144,
|
||||
'codestral-mamba-latest': 262144,
|
||||
'open-codestral-mamba': 262144,
|
||||
'ministral-3b-2410': 131072,
|
||||
'ministral-3b-latest': 131072,
|
||||
'ministral-8b-2410': 131072,
|
||||
'ministral-8b-latest': 131072,
|
||||
'mistral-large-2407': 131072,
|
||||
'mistral-large-2411': 131072,
|
||||
'mistral-large-latest': 131072,
|
||||
'mistral-large-pixtral-2411': 131072,
|
||||
'mistral-tiny-2407': 131072,
|
||||
'mistral-tiny-latest': 131072,
|
||||
'open-mistral-nemo': 131072,
|
||||
'open-mistral-nemo-2407': 131072,
|
||||
'pixtral-12b': 131072,
|
||||
'pixtral-12b-2409': 131072,
|
||||
'pixtral-12b-latest': 131072,
|
||||
'pixtral-large-2411': 131072,
|
||||
'pixtral-large-latest': 131072,
|
||||
'open-mixtral-8x22b': 65536,
|
||||
'open-mixtral-8x22b-2404': 65536,
|
||||
'codestral-2405': 32768,
|
||||
'mistral-embed': 32768,
|
||||
'mistral-large-2402': 32768,
|
||||
'mistral-medium': 32768,
|
||||
'mistral-medium-2312': 32768,
|
||||
'mistral-medium-latest': 32768,
|
||||
'mistral-moderation-2411': 32768,
|
||||
'mistral-moderation-latest': 32768,
|
||||
'mistral-ocr-2503': 32768,
|
||||
'mistral-ocr-latest': 32768,
|
||||
'mistral-saba-2502': 32768,
|
||||
'mistral-saba-latest': 32768,
|
||||
'mistral-small': 32768,
|
||||
'mistral-small-2312': 32768,
|
||||
'mistral-small-2402': 32768,
|
||||
'mistral-small-2409': 32768,
|
||||
'mistral-small-2501': 32768,
|
||||
'mistral-small-2503': 32768,
|
||||
'mistral-small-latest': 32768,
|
||||
'mistral-tiny': 32768,
|
||||
'mistral-tiny-2312': 32768,
|
||||
'open-mistral-7b': 32768,
|
||||
'open-mixtral-8x7b': 32768,
|
||||
};
|
||||
|
||||
// Return context size if model found, otherwise default to 32k
|
||||
return Object.entries(contextMap).find(([key]) => model.includes(key))?.[1] || 32768;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum context size for the Groq model
|
||||
* @param {string} model Model identifier
|
||||
@@ -4312,6 +4462,11 @@ async function onModelChange() {
|
||||
$('#custom_model_id').val(value).trigger('input');
|
||||
}
|
||||
|
||||
if ($(this).is('#model_xai_select')) {
|
||||
console.log('XAI model changed to', value);
|
||||
oai_settings.xai_model = value;
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
@@ -4326,20 +4481,16 @@ async function onModelChange() {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', max_2mil);
|
||||
} else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121') || value.includes('gemini-2.0-flash-thinking-exp-1219')) {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206') || value.includes('gemini-2.0-pro')) {
|
||||
} else if (value.includes('gemini-1.5-pro')) {
|
||||
$('#openai_max_context').attr('max', max_2mil);
|
||||
} else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash') || value.includes('gemini-2.5-pro-exp-03-25') || value.includes('gemini-2.5-pro-preview-03-25')) {
|
||||
} else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash') || value.includes('gemini-2.0-pro') || value.includes('gemini-exp') || value.includes('gemini-2.5-flash') || value.includes('gemini-2.5-pro') || value.includes('learnlm-2.0-flash')) {
|
||||
$('#openai_max_context').attr('max', max_1mil);
|
||||
} else if (value.includes('gemini-1.0-pro') || value === 'gemini-pro') {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else if (value.includes('gemini-1.0-ultra') || value === 'gemini-ultra') {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else if (value.includes('gemma-3')) {
|
||||
} else if (value.includes('gemma-3-27b-it')) {
|
||||
$('#openai_max_context').attr('max', max_128k);
|
||||
} else if (value.includes('gemma-3') || value.includes('learnlm-1.5-pro-experimental')) {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else {
|
||||
$('#openai_max_context').attr('max', max_4k);
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
}
|
||||
let makersuite_max_temp = (value.includes('vision') || value.includes('ultra') || value.includes('gemma')) ? 1.0 : 2.0;
|
||||
oai_settings.temp_openai = Math.min(makersuite_max_temp, oai_settings.temp_openai);
|
||||
@@ -4428,27 +4579,10 @@ async function onModelChange() {
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
} else if (['codestral-latest', 'codestral-mamba-2407', 'codestral-2411-rc5', 'codestral-2412', 'codestral-2501'].includes(oai_settings.mistralai_model)) {
|
||||
$('#openai_max_context').attr('max', max_256k);
|
||||
} else if (['mistral-large-2407', 'mistral-large-2411', 'mistral-large-pixtral-2411', 'mistral-large-latest'].includes(oai_settings.mistralai_model)) {
|
||||
$('#openai_max_context').attr('max', max_128k);
|
||||
} else if (oai_settings.mistralai_model.includes('mistral-nemo')) {
|
||||
$('#openai_max_context').attr('max', max_128k);
|
||||
} else if (oai_settings.mistralai_model.includes('mixtral-8x22b')) {
|
||||
$('#openai_max_context').attr('max', max_64k);
|
||||
} else if (oai_settings.mistralai_model.includes('pixtral')) {
|
||||
$('#openai_max_context').attr('max', max_128k);
|
||||
} else if (oai_settings.mistralai_model.includes('ministral')) {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
}
|
||||
const maxContext = getMistralMaxContext(oai_settings.mistralai_model, oai_settings.max_context_unlocked);
|
||||
$('#openai_max_context').attr('max', maxContext);
|
||||
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
|
||||
//mistral also caps temp at 1.0
|
||||
oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
|
||||
$('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
@@ -4584,6 +4718,22 @@ async function onModelChange() {
|
||||
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.XAI) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
} else if (oai_settings.xai_model.includes('grok-2-vision')) {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else if (oai_settings.xai_model.includes('grok-vision')) {
|
||||
$('#openai_max_context').attr('max', max_8k);
|
||||
} else {
|
||||
$('#openai_max_context').attr('max', max_128k);
|
||||
}
|
||||
|
||||
oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
|
||||
oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1);
|
||||
$('#pres_pen_openai').attr('max', 1).attr('min', 0).val(oai_settings.pres_pen_openai).trigger('input');
|
||||
@@ -4822,6 +4972,19 @@ async function onConnectButtonClick(e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.XAI) {
|
||||
const api_key_xai = String($('#api_key_xai').val()).trim();
|
||||
|
||||
if (api_key_xai.length) {
|
||||
await writeSecret(SECRET_KEYS.XAI, api_key_xai);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.XAI] && !oai_settings.reverse_proxy) {
|
||||
console.log('No secret key saved for XAI');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
startStatusLoading();
|
||||
saveSettingsDebounced();
|
||||
await getStatusOpen();
|
||||
@@ -4878,6 +5041,9 @@ function toggleChatCompletionForms() {
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
|
||||
$('#model_deepseek_select').trigger('change');
|
||||
}
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.XAI) {
|
||||
$('#model_xai_select').trigger('change');
|
||||
}
|
||||
$('[data-source]').each(function () {
|
||||
const validSources = $(this).data('source').split(',');
|
||||
$(this).toggle(validSources.includes(oai_settings.chat_completion_source));
|
||||
@@ -4962,63 +5128,43 @@ export function isImageInliningSupported() {
|
||||
|
||||
// gultra just isn't being offered as multimodal, thanks google.
|
||||
const visionSupportedModels = [
|
||||
'gpt-4-vision',
|
||||
'gemini-2.5-pro-exp-03-25',
|
||||
'gemini-2.5-pro-preview-03-25',
|
||||
'gemini-2.0-pro-exp',
|
||||
'gemini-2.0-pro-exp-02-05',
|
||||
'gemini-2.0-flash-lite-preview',
|
||||
'gemini-2.0-flash-lite-preview-02-05',
|
||||
'gemini-2.0-flash',
|
||||
'gemini-2.0-flash-001',
|
||||
'gemini-2.0-flash-thinking-exp-1219',
|
||||
'gemini-2.0-flash-thinking-exp-01-21',
|
||||
'gemini-2.0-flash-thinking-exp',
|
||||
'gemini-2.0-flash-exp',
|
||||
'gemini-2.0-flash-exp-image-generation',
|
||||
'gemini-1.5-flash',
|
||||
'gemini-1.5-flash-latest',
|
||||
'gemini-1.5-flash-001',
|
||||
'gemini-1.5-flash-002',
|
||||
'gemini-1.5-flash-exp-0827',
|
||||
'gemini-1.5-flash-8b',
|
||||
'gemini-1.5-flash-8b-exp-0827',
|
||||
'gemini-1.5-flash-8b-exp-0924',
|
||||
'gemini-exp-1114',
|
||||
'gemini-exp-1121',
|
||||
'gemini-exp-1206',
|
||||
'gemini-1.0-pro-vision-latest',
|
||||
'gemini-1.5-pro',
|
||||
'gemini-1.5-pro-latest',
|
||||
'gemini-1.5-pro-001',
|
||||
'gemini-1.5-pro-002',
|
||||
'gemini-1.5-pro-exp-0801',
|
||||
'gemini-1.5-pro-exp-0827',
|
||||
'claude-3',
|
||||
'claude-3-5',
|
||||
'claude-3-7',
|
||||
'gpt-4-turbo',
|
||||
'gpt-4o',
|
||||
'gpt-4o-mini',
|
||||
'gpt-4.5-preview',
|
||||
'gpt-4.5-preview-2025-02-27',
|
||||
'o1',
|
||||
'o1-2024-12-17',
|
||||
// OpenAI
|
||||
'chatgpt-4o-latest',
|
||||
'gpt-4-turbo',
|
||||
'gpt-4-vision',
|
||||
'gpt-4.1',
|
||||
'gpt-4.5-preview',
|
||||
'gpt-4o',
|
||||
'o1',
|
||||
'o3',
|
||||
'o4-mini',
|
||||
// 01.AI (Yi)
|
||||
'yi-vision',
|
||||
'pixtral-latest',
|
||||
'pixtral-12b-latest',
|
||||
'pixtral-12b',
|
||||
'pixtral-12b-2409',
|
||||
'pixtral-large-latest',
|
||||
'pixtral-large-2411',
|
||||
'c4ai-aya-vision-8b',
|
||||
'c4ai-aya-vision-32b',
|
||||
// Claude
|
||||
'claude-3',
|
||||
// Cohere
|
||||
'c4ai-aya-vision',
|
||||
// Google AI Studio
|
||||
'gemini-1.5',
|
||||
'gemini-2.0',
|
||||
'gemini-2.5',
|
||||
'gemini-exp-1206',
|
||||
'learnlm',
|
||||
// MistralAI
|
||||
'mistral-small-2503',
|
||||
'mistral-small-latest',
|
||||
'pixtral',
|
||||
// xAI (Grok)
|
||||
'grok-2-vision',
|
||||
'grok-vision',
|
||||
];
|
||||
|
||||
switch (oai_settings.chat_completion_source) {
|
||||
case chat_completion_sources.OPENAI:
|
||||
return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('gpt-4-turbo-preview'));
|
||||
return visionSupportedModels.some(model =>
|
||||
oai_settings.openai_model.includes(model)
|
||||
&& ['gpt-4-turbo-preview', 'o1-mini', 'o3-mini'].some(x => !oai_settings.openai_model.includes(x)),
|
||||
);
|
||||
case chat_completion_sources.MAKERSUITE:
|
||||
return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
|
||||
case chat_completion_sources.CLAUDE:
|
||||
@@ -5033,6 +5179,8 @@ export function isImageInliningSupported() {
|
||||
return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model));
|
||||
case chat_completion_sources.COHERE:
|
||||
return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model));
|
||||
case chat_completion_sources.XAI:
|
||||
return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -5573,6 +5721,7 @@ export function initOpenAI() {
|
||||
|
||||
$('#openai_enable_web_search').on('input', function () {
|
||||
oai_settings.enable_web_search = !!$(this).prop('checked');
|
||||
calculateOpenRouterCost();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
@@ -5629,6 +5778,7 @@ export function initOpenAI() {
|
||||
$('#model_deepseek_select').on('change', onModelChange);
|
||||
$('#model_01ai_select').on('change', onModelChange);
|
||||
$('#model_custom_select').on('change', onModelChange);
|
||||
$('#model_xai_select').on('change', onModelChange);
|
||||
$('#settings_preset_openai').on('change', onSettingsPresetChange);
|
||||
$('#new_oai_preset').on('click', onNewPresetClick);
|
||||
$('#delete_oai_preset').on('click', onDeletePresetClick);
|
||||
|
@@ -22,7 +22,7 @@ import {
|
||||
} from '../script.js';
|
||||
import { persona_description_positions, power_user } from './power-user.js';
|
||||
import { getTokenCountAsync } from './tokenizers.js';
|
||||
import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock } from './utils.js';
|
||||
import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler } from './utils.js';
|
||||
import { debounce_timeout } from './constants.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
import { groups, selected_group } from './group-chats.js';
|
||||
@@ -250,16 +250,18 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
|
||||
const storageKey = 'Personas_PerPage';
|
||||
const listId = '#user_avatar_block';
|
||||
const perPage = Number(accountStorage.getItem(storageKey)) || 5;
|
||||
const sizeChangerOptions = [5, 10, 25, 50, 100, 250, 500, 1000];
|
||||
|
||||
$('#persona_pagination_container').pagination({
|
||||
dataSource: entities,
|
||||
pageSize: perPage,
|
||||
sizeChangerOptions: [5, 10, 25, 50, 100, 250, 500, 1000],
|
||||
sizeChangerOptions,
|
||||
pageRange: 1,
|
||||
pageNumber: savePersonasPage || 1,
|
||||
position: 'top',
|
||||
showPageNumbers: false,
|
||||
showSizeChanger: true,
|
||||
formatSizeChanger: renderPaginationDropdown(perPage, sizeChangerOptions),
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
formatNavigator: PAGINATION_TEMPLATE,
|
||||
@@ -270,9 +272,11 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
|
||||
$(listId).append(getUserAvatarBlock(item));
|
||||
}
|
||||
updatePersonaUIStates();
|
||||
localizePagination($('#persona_pagination_container'));
|
||||
},
|
||||
afterSizeSelectorChange: function (e) {
|
||||
afterSizeSelectorChange: function (e, size) {
|
||||
accountStorage.setItem(storageKey, e.target.value);
|
||||
paginationDropdownChangeHandler(e, size);
|
||||
},
|
||||
afterPaging: function (e) {
|
||||
savePersonasPage = e;
|
||||
|
@@ -71,7 +71,8 @@ export const POPUP_RESULT = {
|
||||
* @property {string} id - The id for the html element
|
||||
* @property {string} label - The label text for the input
|
||||
* @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label
|
||||
* @property {boolean?} [defaultState=false] - The default state when opening the popup (false if not set)
|
||||
* @property {boolean|string|undefined} [defaultState=false] - The default state when opening the popup (false if not set)
|
||||
* @property {string?} [type='checkbox'] - The type of the input (default is checkbox)
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -157,7 +158,7 @@ export class Popup {
|
||||
|
||||
/** @type {POPUP_RESULT|number} */ result;
|
||||
/** @type {any} */ value;
|
||||
/** @type {Map<string,boolean>?} */ inputResults;
|
||||
/** @type {Map<string,string|boolean>?} */ inputResults;
|
||||
/** @type {any} */ cropData;
|
||||
|
||||
/** @type {HTMLElement} */ lastFocus;
|
||||
@@ -260,28 +261,53 @@ export class Popup {
|
||||
return;
|
||||
}
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.classList.add('checkbox_label', 'justifyCenter');
|
||||
label.setAttribute('for', input.id);
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.type = 'checkbox';
|
||||
inputElement.id = input.id;
|
||||
inputElement.checked = input.defaultState ?? false;
|
||||
label.appendChild(inputElement);
|
||||
const labelText = document.createElement('span');
|
||||
labelText.innerText = input.label;
|
||||
labelText.dataset.i18n = input.label;
|
||||
label.appendChild(labelText);
|
||||
if (!input.type || input.type === 'checkbox') {
|
||||
const label = document.createElement('label');
|
||||
label.classList.add('checkbox_label', 'justifyCenter');
|
||||
label.setAttribute('for', input.id);
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.type = 'checkbox';
|
||||
inputElement.id = input.id;
|
||||
inputElement.checked = Boolean(input.defaultState ?? false);
|
||||
label.appendChild(inputElement);
|
||||
const labelText = document.createElement('span');
|
||||
labelText.innerText = input.label;
|
||||
labelText.dataset.i18n = input.label;
|
||||
label.appendChild(labelText);
|
||||
|
||||
if (input.tooltip) {
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.classList.add('fa-solid', 'fa-circle-info', 'opacity50p');
|
||||
tooltip.title = input.tooltip;
|
||||
tooltip.dataset.i18n = '[title]' + input.tooltip;
|
||||
label.appendChild(tooltip);
|
||||
if (input.tooltip) {
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.classList.add('fa-solid', 'fa-circle-info', 'opacity50p');
|
||||
tooltip.title = input.tooltip;
|
||||
tooltip.dataset.i18n = '[title]' + input.tooltip;
|
||||
label.appendChild(tooltip);
|
||||
}
|
||||
|
||||
this.inputControls.appendChild(label);
|
||||
} else if (input.type === 'text') {
|
||||
const label = document.createElement('label');
|
||||
label.classList.add('text_label', 'justifyCenter');
|
||||
label.setAttribute('for', input.id);
|
||||
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.classList.add('text_pole');
|
||||
inputElement.type = 'text';
|
||||
inputElement.id = input.id;
|
||||
inputElement.value = String(input.defaultState ?? '');
|
||||
inputElement.placeholder = input.tooltip ?? '';
|
||||
|
||||
const labelText = document.createElement('span');
|
||||
labelText.innerText = input.label;
|
||||
labelText.dataset.i18n = input.label;
|
||||
|
||||
label.appendChild(labelText);
|
||||
label.appendChild(inputElement);
|
||||
|
||||
this.inputControls.appendChild(label);
|
||||
} else {
|
||||
console.warn('Unknown custom input type. Only checkbox and text are supported.', input);
|
||||
return;
|
||||
}
|
||||
|
||||
this.inputControls.appendChild(label);
|
||||
});
|
||||
|
||||
// Set the default button class
|
||||
@@ -529,7 +555,8 @@ export class Popup {
|
||||
this.inputResults = new Map(this.customInputs.map(input => {
|
||||
/** @type {HTMLInputElement} */
|
||||
const inputControl = this.dlg.querySelector(`#${input.id}`);
|
||||
return [inputControl.id, inputControl.checked];
|
||||
const value = input.type === 'text' ? inputControl.value : inputControl.checked;
|
||||
return [inputControl.id, value];
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -619,7 +646,7 @@ export class Popup {
|
||||
/** @readonly @type {Popup[]} Remember all popups */
|
||||
popups: [],
|
||||
|
||||
/** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, boolean>?}?} Last popup result */
|
||||
/** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, string|boolean>?}?} Last popup result */
|
||||
lastResult: null,
|
||||
|
||||
/** @returns {boolean} Checks if any modal popup dialog is open */
|
||||
|
@@ -71,8 +71,8 @@ export {
|
||||
|
||||
export const MAX_CONTEXT_DEFAULT = 8192;
|
||||
export const MAX_RESPONSE_DEFAULT = 2048;
|
||||
const MAX_CONTEXT_UNLOCKED = 200 * 1024;
|
||||
const MAX_RESPONSE_UNLOCKED = 32 * 1024;
|
||||
const MAX_CONTEXT_UNLOCKED = 512 * 1024;
|
||||
const MAX_RESPONSE_UNLOCKED = 64 * 1024;
|
||||
const unlockedMaxContextStep = 512;
|
||||
const maxContextMin = 512;
|
||||
const maxContextStep = 64;
|
||||
@@ -244,7 +244,6 @@ let power_user = {
|
||||
chat_start: defaultChatStart,
|
||||
example_separator: defaultExampleSeparator,
|
||||
use_stop_strings: true,
|
||||
allow_jailbreak: false,
|
||||
names_as_stop_strings: true,
|
||||
},
|
||||
|
||||
@@ -255,6 +254,7 @@ let power_user = {
|
||||
enabled: true,
|
||||
name: 'Neutral - Chat',
|
||||
content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.',
|
||||
post_history: '',
|
||||
},
|
||||
|
||||
reasoning: {
|
||||
@@ -334,7 +334,6 @@ const contextControls = [
|
||||
{ id: 'context_example_separator', property: 'example_separator', isCheckbox: false, isGlobalSetting: false },
|
||||
{ id: 'context_chat_start', property: 'chat_start', isCheckbox: false, isGlobalSetting: false },
|
||||
{ id: 'context_use_stop_strings', property: 'use_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: false },
|
||||
{ id: 'context_allow_jailbreak', property: 'allow_jailbreak', isCheckbox: true, isGlobalSetting: false, defaultValue: false },
|
||||
{ id: 'context_names_as_stop_strings', property: 'names_as_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: true },
|
||||
|
||||
// Existing power user settings
|
||||
|
@@ -902,6 +902,12 @@ export async function initPresetManager() {
|
||||
|
||||
await presetManager.renamePreset(newName);
|
||||
|
||||
if (apiId === 'openai') {
|
||||
// This is a horrible mess, but prevents the renamed preset from being corrupted.
|
||||
$('#update_oai_preset').trigger('click');
|
||||
return;
|
||||
}
|
||||
|
||||
const successToast = !presetManager.isAdvancedFormatting() ? t`Preset renamed` : t`Template renamed`;
|
||||
toastr.success(successToast);
|
||||
});
|
||||
|
@@ -109,6 +109,8 @@ export function extractReasoningFromData(data, {
|
||||
switch (chatCompletionSource ?? oai_settings.chat_completion_source) {
|
||||
case chat_completion_sources.DEEPSEEK:
|
||||
return data?.choices?.[0]?.message?.reasoning_content ?? '';
|
||||
case chat_completion_sources.XAI:
|
||||
return data?.choices?.[0]?.message?.reasoning_content ?? '';
|
||||
case chat_completion_sources.OPENROUTER:
|
||||
return data?.choices?.[0]?.message?.reasoning ?? '';
|
||||
case chat_completion_sources.MAKERSUITE:
|
||||
|
@@ -42,6 +42,7 @@ export const SECRET_KEYS = {
|
||||
DEEPSEEK: 'api_key_deepseek',
|
||||
SERPER: 'api_key_serper',
|
||||
FALAI: 'api_key_falai',
|
||||
XAI: 'api_key_xai',
|
||||
};
|
||||
|
||||
const INPUT_MAP = {
|
||||
@@ -76,6 +77,7 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',
|
||||
[SECRET_KEYS.GENERIC]: '#api_key_generic',
|
||||
[SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',
|
||||
[SECRET_KEYS.XAI]: '#api_key_xai',
|
||||
};
|
||||
|
||||
async function clearSecret() {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Fuse, DOMPurify } from '../lib.js';
|
||||
import { flashHighlight } from './utils.js';
|
||||
|
||||
import {
|
||||
Generate,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
extractMessageBias,
|
||||
generateQuietPrompt,
|
||||
generateRaw,
|
||||
getFirstDisplayedMessageId,
|
||||
getThumbnailUrl,
|
||||
is_send_press,
|
||||
main_api,
|
||||
@@ -313,6 +315,11 @@ export function initDefaultSlashCommands() {
|
||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'Optional custom display name to use for this system narrator message.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'return',
|
||||
description: 'The way how you want the return value to be provided',
|
||||
@@ -2077,8 +2084,9 @@ export function initDefaultSlashCommands() {
|
||||
name: 'replace',
|
||||
aliases: ['re'],
|
||||
callback: (async ({ mode = 'literal', pattern, replacer = '' }, text) => {
|
||||
if (pattern === '')
|
||||
if (!pattern) {
|
||||
throw new Error('Argument of \'pattern=\' cannot be empty');
|
||||
}
|
||||
switch (mode) {
|
||||
case 'literal':
|
||||
return text.replaceAll(pattern, replacer);
|
||||
@@ -2121,14 +2129,160 @@ export function initDefaultSlashCommands() {
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<pre>/let x Blue house and blue car || </pre>
|
||||
<pre>/replace pattern="blue" {{var::x}} | /echo |/# Blue house and car ||</pre>
|
||||
<pre>/replace pattern="blue" replacer="red" {{var::x}} | /echo |/# Blue house and red car ||</pre>
|
||||
<pre>/replace mode=regex pattern="/blue/i" replacer="red" {{var::x}} | /echo |/# red house and blue car ||</pre>
|
||||
<pre>/replace mode=regex pattern="/blue/gi" replacer="red" {{var::x}} | /echo |/# red house and red car ||</pre>
|
||||
<pre><code class="language-stscript">/let x Blue house and blue car || </code></pre>
|
||||
<pre><code class="language-stscript">/replace pattern="blue" {{var::x}} | /echo |/# Blue house and car ||</code></pre>
|
||||
<pre><code class="language-stscript">/replace pattern="blue" replacer="red" {{var::x}} | /echo |/# Blue house and red car ||</code></pre>
|
||||
<pre><code class="language-stscript">/replace mode=regex pattern="/blue/i" replacer="red" {{var::x}} | /echo |/# red house and blue car ||</code></pre>
|
||||
<pre><code class="language-stscript">/replace mode=regex pattern="/blue/gi" replacer="red" {{var::x}} | /echo |/# red house and red car ||</code></pre>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'test',
|
||||
callback: (({ pattern }, text) => {
|
||||
if (!pattern) {
|
||||
throw new Error('Argument of \'pattern=\' cannot be empty');
|
||||
}
|
||||
const re = regexFromString(pattern.toString());
|
||||
if (!re) {
|
||||
throw new Error('The value of \'pattern\' argument is not a valid regular expression.');
|
||||
}
|
||||
return JSON.stringify(re.test(text.toString()));
|
||||
}),
|
||||
returns: 'true | false',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'pattern', 'pattern to find', [ARGUMENT_TYPE.STRING], true, false,
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'text to test', [ARGUMENT_TYPE.STRING], true, false,
|
||||
),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Tests text for a regular expression match.
|
||||
</div>
|
||||
<div>
|
||||
Returns <code>true</code> if the match is found, <code>false</code> otherwise.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<pre><code class="language-stscript">/let x Blue house and green car ||</code></pre>
|
||||
<pre><code class="language-stscript">/test pattern="green" {{var::x}} | /echo |/# true ||</code></pre>
|
||||
<pre><code class="language-stscript">/test pattern="blue" {{var::x}} | /echo |/# false ||</code></pre>
|
||||
<pre><code class="language-stscript">/test pattern="/blue/i" {{var::x}} | /echo |/# true ||</code></pre>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'match',
|
||||
callback: (({ pattern }, text) => {
|
||||
if (!pattern) {
|
||||
throw new Error('Argument of \'pattern=\' cannot be empty');
|
||||
}
|
||||
const re = regexFromString(pattern.toString());
|
||||
if (!re) {
|
||||
throw new Error('The value of \'pattern\' argument is not a valid regular expression.');
|
||||
}
|
||||
if (re.flags.includes('g')) {
|
||||
return JSON.stringify([...text.toString().matchAll(re)]);
|
||||
} else {
|
||||
const match = text.toString().match(re);
|
||||
return match ? JSON.stringify(match) : '';
|
||||
}
|
||||
}),
|
||||
returns: 'group array for each match',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'pattern', 'pattern to find', [ARGUMENT_TYPE.STRING], true, false,
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'text to match against', [ARGUMENT_TYPE.STRING], true, false,
|
||||
),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Retrieves regular expression matches in the given text
|
||||
</div>
|
||||
<div>
|
||||
Returns an array of groups (with the first group being the full match). If the regex contains the global flag (i.e. <code>/g</code>),
|
||||
multiple nested arrays are returned for each match. If the regex is global, returns <code>[]</code> if no matches are found,
|
||||
otherwise it returns an empty string.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<pre><code class="language-stscript">/let x color_green green lamp color_blue ||</code></pre>
|
||||
<pre><code class="language-stscript">/match pattern="green" {{var::x}} | /echo |/# [ "green" ] ||</code></pre>
|
||||
<pre><code class="language-stscript">/match pattern="color_(\\w+)" {{var::x}} | /echo |/# [ "color_green", "green" ] ||</code></pre>
|
||||
<pre><code class="language-stscript">/match pattern="/color_(\\w+)/g" {{var::x}} | /echo |/# [ [ "color_green", "green" ], [ "color_blue", "blue" ] ] ||</code></pre>
|
||||
<pre><code class="language-stscript">/match pattern="orange" {{var::x}} | /echo |/# ||</code></pre>
|
||||
<pre><code class="language-stscript">/match pattern="/orange/g" {{var::x}} | /echo |/# [] ||</code></pre>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'chat-jump',
|
||||
aliases: ['chat-scrollto', 'floor-teleport'],
|
||||
callback: async (_, index) => {
|
||||
const messageIndex = Number(index);
|
||||
|
||||
if (isNaN(messageIndex) || messageIndex < 0 || messageIndex >= chat.length) {
|
||||
toastr.warning(t`Invalid message index: ${index}. Please enter a number between 0 and ${chat.length}.`);
|
||||
console.warn(`WARN: Invalid message index provided for /chat-jump: ${index}. Max index: ${chat.length}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
// Load more messages if needed
|
||||
const firstDisplayedMessageId = getFirstDisplayedMessageId();
|
||||
if (isFinite(firstDisplayedMessageId) && messageIndex < firstDisplayedMessageId) {
|
||||
const needToLoadCount = firstDisplayedMessageId - messageIndex;
|
||||
await showMoreMessages(needToLoadCount);
|
||||
await delay(1);
|
||||
}
|
||||
|
||||
const chatContainer = document.getElementById('chat');
|
||||
const messageElement = document.querySelector(`#chat .mes[mesid="${messageIndex}"]`);
|
||||
|
||||
if (messageElement instanceof HTMLElement && chatContainer instanceof HTMLElement) {
|
||||
const elementRect = messageElement.getBoundingClientRect();
|
||||
const containerRect = chatContainer.getBoundingClientRect();
|
||||
|
||||
const scrollPosition = elementRect.top - containerRect.top + chatContainer.scrollTop;
|
||||
chatContainer.scrollTo({
|
||||
top: scrollPosition,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
|
||||
flashHighlight($(messageElement), 2000);
|
||||
} else {
|
||||
toastr.warning(t`Could not find element for message ${messageIndex}. It might not be rendered yet or the index is invalid.`);
|
||||
console.warn(`WARN: Element not found for message index ${messageIndex} in /chat-jump.`);
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The message index (0-based) to scroll to.',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.messages(),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Scrolls the chat view to the specified message index. Index starts at 0.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong> <pre><code>/chat-jump 10</code></pre> Scrolls to the 11th message (id=10).
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
|
||||
registerVariableCommands();
|
||||
}
|
||||
@@ -3702,7 +3856,7 @@ export async function sendMessageAs(args, text) {
|
||||
|
||||
export async function sendNarratorMessage(args, text) {
|
||||
text = String(text ?? '');
|
||||
const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;
|
||||
const name = args.name ?? (chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT);
|
||||
// Messages that do nothing but set bias will be hidden from the context
|
||||
const bias = extractMessageBias(text);
|
||||
const isSystem = bias && !removeMacros(text).length;
|
||||
@@ -3942,6 +4096,7 @@ function getModelOptions(quiet) {
|
||||
{ id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },
|
||||
{ id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },
|
||||
{ id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },
|
||||
{ id: 'model_xai_select', api: 'openai', type: chat_completion_sources.XAI },
|
||||
{ id: 'model_novel_select', api: 'novel', type: null },
|
||||
{ id: 'horde_model', api: 'koboldhorde', type: null },
|
||||
];
|
||||
|
@@ -50,6 +50,8 @@ import {
|
||||
unshallowCharacter,
|
||||
deleteLastMessage,
|
||||
getCharacterCardFields,
|
||||
swipe_right,
|
||||
swipe_left,
|
||||
} from '../script.js';
|
||||
import {
|
||||
extension_settings,
|
||||
@@ -196,6 +198,7 @@ export function getContext() {
|
||||
humanizedDateTime,
|
||||
updateMessageBlock,
|
||||
appendMediaToMessage,
|
||||
swipe: { left: swipe_left, right: swipe_right },
|
||||
variables: {
|
||||
local: {
|
||||
get: getLocalVariable,
|
||||
|
@@ -17,6 +17,7 @@ export let system_prompts = [];
|
||||
const $enabled = $('#sysprompt_enabled');
|
||||
const $select = $('#sysprompt_select');
|
||||
const $content = $('#sysprompt_content');
|
||||
const $postHistory = $('#sysprompt_post_history');
|
||||
const $contentBlock = $('#SystemPromptBlock');
|
||||
|
||||
async function migrateSystemPromptFromInstructMode() {
|
||||
@@ -25,6 +26,7 @@ async function migrateSystemPromptFromInstructMode() {
|
||||
delete power_user.instruct.system_prompt;
|
||||
power_user.sysprompt.enabled = power_user.instruct.enabled;
|
||||
power_user.sysprompt.content = prompt;
|
||||
power_user.sysprompt.post_history = '';
|
||||
|
||||
const existingPromptName = system_prompts.find(x => x.content === prompt)?.name;
|
||||
|
||||
@@ -59,7 +61,8 @@ export async function loadSystemPrompts(data) {
|
||||
|
||||
$enabled.prop('checked', power_user.sysprompt.enabled);
|
||||
$select.val(power_user.sysprompt.name);
|
||||
$content.val(power_user.sysprompt.content);
|
||||
$content.val(power_user.sysprompt.content || '');
|
||||
$postHistory.val(power_user.sysprompt.post_history || '');
|
||||
if (!CSS.supports('field-sizing', 'content')) {
|
||||
await resetScrollHeight($content);
|
||||
}
|
||||
@@ -165,13 +168,17 @@ export function initSystemPrompts() {
|
||||
const name = String($(this).val());
|
||||
const prompt = system_prompts.find(p => p.name === name);
|
||||
if (prompt) {
|
||||
$content.val(prompt.content);
|
||||
$content.val(prompt.content || '');
|
||||
$postHistory.val(prompt.post_history || '');
|
||||
|
||||
if (!CSS.supports('field-sizing', 'content')) {
|
||||
await resetScrollHeight($content);
|
||||
await resetScrollHeight($postHistory);
|
||||
}
|
||||
|
||||
power_user.sysprompt.name = name;
|
||||
power_user.sysprompt.content = prompt.content;
|
||||
power_user.sysprompt.content = prompt.content || '';
|
||||
power_user.sysprompt.post_history = prompt.post_history || '';
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
@@ -181,6 +188,11 @@ export function initSystemPrompts() {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$postHistory.on('input', function () {
|
||||
power_user.sysprompt.post_history = String($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'sysprompt',
|
||||
aliases: ['system-prompt'],
|
||||
|
@@ -27,7 +27,7 @@ import { debounce_timeout } from './constants.js';
|
||||
import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js';
|
||||
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { t } from './i18n.js';
|
||||
import { t, translate } from './i18n.js';
|
||||
|
||||
export {
|
||||
TAG_FOLDER_TYPES,
|
||||
@@ -318,7 +318,7 @@ function getTagBlock(tag, entities, hidden = 0, isUseless = false) {
|
||||
template.find('.avatar').css({ 'background-color': tag.color, 'color': tag.color2 }).attr('title', `[Folder] ${tag.name}`);
|
||||
template.find('.ch_name').text(tag.name).attr('title', `[Folder] ${tag.name}`);
|
||||
template.find('.bogus_folder_hidden_counter').text(hidden > 0 ? `${hidden} hidden` : '');
|
||||
template.find('.bogus_folder_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`);
|
||||
template.find('.bogus_folder_counter').text(`${count} ` + (count != 1 ? t`characters` : t`character`));
|
||||
template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon);
|
||||
if (isUseless) template.addClass('useless');
|
||||
|
||||
@@ -1057,7 +1057,7 @@ function appendTagToList(listElement, tag, { removable = false, isFilter = false
|
||||
tagElement.attr('title', tag.title);
|
||||
}
|
||||
if (tag.icon) {
|
||||
tagElement.find('.tag_name').text('').attr('title', `${tag.name} ${tag.title || ''}`.trim()).addClass(tag.icon);
|
||||
tagElement.find('.tag_name').text('').attr('title', `${translate(tag.name)} ${tag.title || ''}`.trim()).addClass(tag.icon);
|
||||
tagElement.addClass('actionable');
|
||||
}
|
||||
|
||||
@@ -1644,6 +1644,7 @@ function updateDrawTagFolder(element, tag) {
|
||||
|
||||
// Draw/update css attributes for this class
|
||||
folderElement.attr('title', tagFolder.tooltip);
|
||||
folderElement.attr('data-i18n', '[title]' + tagFolder.tooltip);
|
||||
const indicator = folderElement.find('.tag_folder_indicator');
|
||||
indicator.text(tagFolder.icon);
|
||||
indicator.css('color', tagFolder.color);
|
||||
@@ -1655,14 +1656,7 @@ async function onTagDeleteClick() {
|
||||
const tag = tags.find(x => x.id === id);
|
||||
const otherTags = sortTags(tags.filter(x => x.id !== id).map(x => ({ id: x.id, name: x.name })));
|
||||
|
||||
const popupContent = $(`
|
||||
<h3>Delete Tag</h3>
|
||||
<div>Do you want to delete the tag <div id="tag_to_delete" class="tags_inline inline-flex margin-r2"></div>?</div>
|
||||
<div class="m-t-2 marginBot5">If you want to merge all references to this tag into another tag, select it below:</div>
|
||||
<select id="merge_tag_select">
|
||||
<option value="">--- None ---</option>
|
||||
${otherTags.map(x => `<option value="${x.id}">${x.name}</option>`).join('')}
|
||||
</select>`);
|
||||
const popupContent = $(await renderTemplateAsync('deleteTag', { otherTags }));
|
||||
|
||||
appendTagToList(popupContent.find('#tag_to_delete'), tag);
|
||||
|
||||
|
9
public/scripts/templates/deleteTag.html
Normal file
9
public/scripts/templates/deleteTag.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<h3 data-i18n="Delete Tag">Delete Tag</h3>
|
||||
<div><span data-i18n="Do you want to delete the tag">Do you want to delete the tag</span> <div id="tag_to_delete" class="tags_inline inline-flex margin-r2"></div>?</div>
|
||||
<div class="m-t-2 marginBot5" data-i18n="If you want to merge all references to this tag into another tag, select it below:">If you want to merge all references to this tag into another tag, select it below:</div>
|
||||
<select id="merge_tag_select">
|
||||
<option value="">--- None ---</option>
|
||||
{{#each otherTags}}
|
||||
<option value="{{this.id}}">{{this.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
7
public/scripts/templates/emptyBlock.html
Normal file
7
public/scripts/templates/emptyBlock.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="text_block empty_block">
|
||||
<i class="fa-solid {{icon}} fa-4x"></i>
|
||||
<h1>{{text}}</h1>
|
||||
<p data-i18n="There are no items to display.">
|
||||
There are no items to display.
|
||||
</p>
|
||||
</div>
|
6
public/scripts/templates/hiddenBlock.html
Normal file
6
public/scripts/templates/hiddenBlock.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="text_block hidden_block">
|
||||
<small>
|
||||
<p>{{text}}</p>
|
||||
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Characters and groups hidden by filters or closed folders" title="Characters and groups hidden by filters or closed folders"></div>
|
||||
</small>
|
||||
</div>
|
@@ -83,7 +83,10 @@
|
||||
<div class="tokenItemizingSubclass">{{scenarioTextTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Examples:</div>
|
||||
<div class=" flex1 tokenItemizingSubclass">
|
||||
<span>-- Examples:</span>
|
||||
{{#if examplesCount}}<small>({{examplesCount}})</small>{{/if}}
|
||||
</div>
|
||||
<div class="tokenItemizingSubclass">{{examplesStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container ">
|
||||
@@ -96,7 +99,10 @@
|
||||
<div class="">{{worldInfoStringTokens}}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: palegreen;"><span data-i18n="Chat History:">Chat History:</span></div>
|
||||
<div class="flex1" style="color: palegreen;">
|
||||
<span data-i18n="Chat History:">Chat History:</span>
|
||||
{{#if messagesCount}}<small>({{messagesCount}})</small>{{/if}}
|
||||
</div>
|
||||
<div class="">{{ActualChatHistoryTokens}}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
|
@@ -51,7 +51,10 @@
|
||||
<div class="tokenItemizingSubclass">{{scenarioTextTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class=" flex1 tokenItemizingSubclass">-- Examples:</div>
|
||||
<div class=" flex1 tokenItemizingSubclass">
|
||||
<span>-- Examples:</span>
|
||||
{{#if examplesCount}}<small>({{examplesCount}})</small>{{/if}}
|
||||
</div>
|
||||
<div class="tokenItemizingSubclass"> {{examplesStringTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
@@ -68,7 +71,10 @@
|
||||
<div class="">{{worldInfoStringTokens}}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container">
|
||||
<div class="flex1" style="color: palegreen;">Chat History:</div>
|
||||
<div class="flex1" style="color: palegreen;">
|
||||
<span data-i18n="Chat History:">Chat History:</span>
|
||||
{{#if messagesCount}}<small>({{messagesCount}})</small>{{/if}}
|
||||
</div>
|
||||
<div class=""> {{ActualChatHistoryTokens}}</div>
|
||||
</div>
|
||||
<div class="wide100p flex-container flexNoGap flexFlowColumn">
|
||||
|
@@ -7,6 +7,7 @@ import { renderTemplateAsync } from './templates.js';
|
||||
import { POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||
import { t } from './i18n.js';
|
||||
import { accountStorage } from './util/AccountStorage.js';
|
||||
import { localizePagination, PAGINATION_TEMPLATE } from './utils.js';
|
||||
|
||||
let mancerModels = [];
|
||||
let togetherModels = [];
|
||||
@@ -41,12 +42,7 @@ const OPENROUTER_PROVIDERS = [
|
||||
'Avian',
|
||||
'Lambda',
|
||||
'Azure',
|
||||
'Modal',
|
||||
'AnyScale',
|
||||
'Replicate',
|
||||
'Perplexity',
|
||||
'Recursal',
|
||||
'OctoAI',
|
||||
'DeepSeek',
|
||||
'Infermatic',
|
||||
'AI21',
|
||||
@@ -54,10 +50,12 @@ const OPENROUTER_PROVIDERS = [
|
||||
'Inflection',
|
||||
'xAI',
|
||||
'Cloudflare',
|
||||
'SF Compute',
|
||||
'Minimax',
|
||||
'Nineteen',
|
||||
'Liquid',
|
||||
'GMICloud',
|
||||
'Stealth',
|
||||
'NCompass',
|
||||
'InferenceNet',
|
||||
'Friendli',
|
||||
'AionLabs',
|
||||
@@ -69,14 +67,16 @@ const OPENROUTER_PROVIDERS = [
|
||||
'Targon',
|
||||
'Ubicloud',
|
||||
'Parasail',
|
||||
'01.AI',
|
||||
'HuggingFace',
|
||||
'Phala',
|
||||
'Cent-ML',
|
||||
'Venice',
|
||||
'OpenInference',
|
||||
'Atoma',
|
||||
'Enfer',
|
||||
'Mancer',
|
||||
'Mancer 2',
|
||||
'Hyperbolic',
|
||||
'Hyperbolic 2',
|
||||
'Lynn 2',
|
||||
'Lynn',
|
||||
'Reflection',
|
||||
];
|
||||
|
||||
@@ -362,9 +362,7 @@ export async function loadFeatherlessModels(data) {
|
||||
showSizeChanger: false,
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
formatNavigator: function (currentPage, totalPage) {
|
||||
return (currentPage - 1) * perPage + 1 + ' - ' + currentPage * perPage + ' of ' + totalPage * perPage;
|
||||
},
|
||||
formatNavigator: PAGINATION_TEMPLATE,
|
||||
showNavigator: true,
|
||||
callback: function (modelsOnPage, pagination) {
|
||||
modelCardBlock.innerHTML = '';
|
||||
@@ -386,15 +384,15 @@ export async function loadFeatherlessModels(data) {
|
||||
|
||||
const modelClassDiv = document.createElement('div');
|
||||
modelClassDiv.classList.add('model-class');
|
||||
modelClassDiv.textContent = `Class: ${model.model_class || 'N/A'}`;
|
||||
modelClassDiv.textContent = t`Class` + `: ${model.model_class || 'N/A'}`;
|
||||
|
||||
const contextLengthDiv = document.createElement('div');
|
||||
contextLengthDiv.classList.add('model-context-length');
|
||||
contextLengthDiv.textContent = `Context Length: ${model.context_length}`;
|
||||
contextLengthDiv.textContent = t`Context Length` + `: ${model.context_length}`;
|
||||
|
||||
const dateAddedDiv = document.createElement('div');
|
||||
dateAddedDiv.classList.add('model-date-added');
|
||||
dateAddedDiv.textContent = `Added On: ${new Date(model.created * 1000).toLocaleDateString()}`;
|
||||
dateAddedDiv.textContent = t`Added On` + `: ${new Date(model.created * 1000).toLocaleDateString()}`;
|
||||
|
||||
detailsContainer.appendChild(modelClassDiv);
|
||||
detailsContainer.appendChild(contextLengthDiv);
|
||||
@@ -418,6 +416,7 @@ export async function loadFeatherlessModels(data) {
|
||||
|
||||
// Update the current page value whenever the page changes
|
||||
featherlessCurrentPage = pagination.pageNumber;
|
||||
localizePagination(paginationContainer);
|
||||
},
|
||||
afterSizeSelectorChange: function (e) {
|
||||
const newPerPage = e.target.value;
|
||||
@@ -923,6 +922,10 @@ export function getCurrentDreamGenModelTokenizer() {
|
||||
return tokenizers.YI;
|
||||
} else if (model.id.startsWith('opus-v1-xl')) {
|
||||
return tokenizers.LLAMA;
|
||||
} else if (model.id.startsWith('lucid-v1-medium')) {
|
||||
return tokenizers.NEMO;
|
||||
} else if (model.id.startsWith('lucid-v1-extra-large')) {
|
||||
return tokenizers.LLAMA3;
|
||||
} else {
|
||||
return tokenizers.MISTRAL;
|
||||
}
|
||||
|
@@ -1146,7 +1146,7 @@ function tryParseStreamingError(response, decoded) {
|
||||
// No JSON. Do nothing.
|
||||
}
|
||||
|
||||
const message = data?.error?.message || data?.message || data?.detail;
|
||||
const message = data?.error?.message || data?.error || data?.message || data?.detail;
|
||||
|
||||
if (message) {
|
||||
toastr.error(message, 'Text Completion API');
|
||||
|
@@ -586,6 +586,7 @@ export class ToolManager {
|
||||
chat_completion_sources.DEEPSEEK,
|
||||
chat_completion_sources.MAKERSUITE,
|
||||
chat_completion_sources.AI21,
|
||||
chat_completion_sources.XAI,
|
||||
];
|
||||
return supportedSources.includes(oai_settings.chat_completion_source);
|
||||
}
|
||||
|
@@ -20,7 +20,46 @@ import { getCurrentLocale, t } from './i18n.js';
|
||||
* Pagination status string template.
|
||||
* @type {string}
|
||||
*/
|
||||
export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> of <%= totalNumber %>';
|
||||
export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> .. <%= totalNumber %>';
|
||||
|
||||
export const localizePagination = function(container) {
|
||||
container.find('[title="Next page"]').attr('title', t`Next page`);
|
||||
container.find('[title="Previous page"]').attr('title', t`Previous page`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a dropdown for selecting page size in pagination.
|
||||
* @param {number} pageSize Page size
|
||||
* @param {number[]} sizeChangerOptions Array of page size options
|
||||
* @returns {string} The rendered dropdown element as a string
|
||||
*/
|
||||
export const renderPaginationDropdown = function(pageSize, sizeChangerOptions) {
|
||||
const sizeSelect = document.createElement('select');
|
||||
sizeSelect.classList.add('J-paginationjs-size-select');
|
||||
|
||||
if (sizeChangerOptions.indexOf(pageSize) === -1) {
|
||||
sizeChangerOptions.unshift(pageSize);
|
||||
sizeChangerOptions.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
for (let i = 0; i < sizeChangerOptions.length; i++) {
|
||||
const option = document.createElement('option');
|
||||
option.value = `${sizeChangerOptions[i]}`;
|
||||
option.textContent = `${sizeChangerOptions[i]} ${t`/ page`}`;
|
||||
if (sizeChangerOptions[i] === pageSize) {
|
||||
option.setAttribute('selected', 'selected');
|
||||
}
|
||||
sizeSelect.appendChild(option);
|
||||
}
|
||||
|
||||
return sizeSelect.outerHTML;
|
||||
};
|
||||
|
||||
export const paginationDropdownChangeHandler = function(event, size) {
|
||||
let dropdown = $(event?.originalEvent?.currentTarget || event.delegateTarget).find('select');
|
||||
dropdown.find('[selected]').removeAttr('selected');
|
||||
dropdown.find(`[value=${size}]`).attr('selected', '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigation options for pagination.
|
||||
@@ -1047,7 +1086,7 @@ export function getImageSizeFromDataURL(dataUrl) {
|
||||
|
||||
/**
|
||||
* Gets the filename of the character avatar without extension
|
||||
* @param {number?} [chid=null] - Character ID. If not provided, uses the current character ID
|
||||
* @param {string|number?} [chid=null] - Character ID. If not provided, uses the current character ID
|
||||
* @param {object} [options={}] - Options arguments
|
||||
* @param {string?} [options.manualAvatarKey=null] - Manually take the following avatar key, instead of using the chid to determine the name
|
||||
* @returns {string?} The filename of the character avatar without extension, or null if the character ID is invalid
|
||||
|
@@ -97,12 +97,29 @@ export const MAX_SCAN_DEPTH = 1000;
|
||||
const KNOWN_DECORATORS = ['@@activate', '@@dont_activate'];
|
||||
|
||||
// Typedef area
|
||||
/**
|
||||
* @typedef {object} WIGlobalScanData The chat-independent data to be scanned. Each of
|
||||
* these fields can be enabled for scanning per entry.
|
||||
* @property {string} personaDescription User persona description
|
||||
* @property {string} characterDescription Character description
|
||||
* @property {string} characterPersonality Character personality
|
||||
* @property {string} characterDepthPrompt Character depth prompt (sometimes referred to as character notes)
|
||||
* @property {string} scenario Character defined scenario
|
||||
* @property {string} creatorNotes Character creator notes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} WIScanEntry The entry that triggered the scan
|
||||
* @property {number} [scanDepth] The depth of the scan
|
||||
* @property {boolean} [caseSensitive] If the scan is case sensitive
|
||||
* @property {boolean} [matchWholeWords] If the scan should match whole words
|
||||
* @property {boolean} [useGroupScoring] If the scan should use group scoring
|
||||
* @property {boolean} [matchPersonaDescription] If the scan should match against the persona description
|
||||
* @property {boolean} [matchCharacterDescription] If the scan should match against the character description
|
||||
* @property {boolean} [matchCharacterPersonality] If the scan should match against the character personality
|
||||
* @property {boolean} [matchCharacterDepthPrompt] If the scan should match against the character depth prompt
|
||||
* @property {boolean} [matchScenario] If the scan should match against the character scenario
|
||||
* @property {boolean} [matchCreatorNotes] If the scan should match against the creator notes
|
||||
* @property {number} [uid] The UID of the entry that triggered the scan
|
||||
* @property {string} [world] The world info book of origin of the entry
|
||||
* @property {string[]} [key] The primary keys to scan for
|
||||
@@ -138,6 +155,11 @@ class WorldInfoBuffer {
|
||||
*/
|
||||
static externalActivations = new Map();
|
||||
|
||||
/**
|
||||
* @type {WIGlobalScanData} Chat independent data to be scanned, such as persona and character descriptions
|
||||
*/
|
||||
#globalScanData = null;
|
||||
|
||||
/**
|
||||
* @type {string[]} Array of messages sorted by ascending depth
|
||||
*/
|
||||
@@ -166,9 +188,11 @@ class WorldInfoBuffer {
|
||||
/**
|
||||
* Initialize the buffer with the given messages.
|
||||
* @param {string[]} messages Array of messages to add to the buffer
|
||||
* @param {WIGlobalScanData} globalScanData Chat independent context to be scanned
|
||||
*/
|
||||
constructor(messages) {
|
||||
constructor(messages, globalScanData) {
|
||||
this.#initDepthBuffer(messages);
|
||||
this.#globalScanData = globalScanData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,6 +249,25 @@ class WorldInfoBuffer {
|
||||
const JOINER = '\n' + MATCHER;
|
||||
let result = MATCHER + this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER);
|
||||
|
||||
if (entry.matchPersonaDescription && this.#globalScanData.personaDescription) {
|
||||
result += JOINER + this.#globalScanData.personaDescription;
|
||||
}
|
||||
if (entry.matchCharacterDescription && this.#globalScanData.characterDescription) {
|
||||
result += JOINER + this.#globalScanData.characterDescription;
|
||||
}
|
||||
if (entry.matchCharacterPersonality && this.#globalScanData.characterPersonality) {
|
||||
result += JOINER + this.#globalScanData.characterPersonality;
|
||||
}
|
||||
if (entry.matchCharacterDepthPrompt && this.#globalScanData.characterDepthPrompt) {
|
||||
result += JOINER + this.#globalScanData.characterDepthPrompt;
|
||||
}
|
||||
if (entry.matchScenario && this.#globalScanData.scenario) {
|
||||
result += JOINER + this.#globalScanData.scenario;
|
||||
}
|
||||
if (entry.matchCreatorNotes && this.#globalScanData.creatorNotes) {
|
||||
result += JOINER + this.#globalScanData.creatorNotes;
|
||||
}
|
||||
|
||||
if (this.#injectBuffer.length > 0) {
|
||||
result += JOINER + this.#injectBuffer.join(JOINER);
|
||||
}
|
||||
@@ -756,6 +799,7 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
|
||||
* @param {string[]} chat - The chat messages to scan, in reverse order.
|
||||
* @param {number} maxContext - The maximum context size of the generation.
|
||||
* @param {boolean} isDryRun - If true, the function will not emit any events.
|
||||
* @param {WIGlobalScanData} globalScanData Chat independent context to be scanned
|
||||
* @typedef {object} WIPromptResult
|
||||
* @property {string} worldInfoString - Complete world info string
|
||||
* @property {string} worldInfoBefore - World info that goes before the prompt
|
||||
@@ -766,10 +810,10 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
|
||||
* @property {Array} anAfter - Array of entries after Author's Note
|
||||
* @returns {Promise<WIPromptResult>} The world info string and depth.
|
||||
*/
|
||||
export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
|
||||
export async function getWorldInfoPrompt(chat, maxContext, isDryRun, globalScanData) {
|
||||
let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';
|
||||
|
||||
const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun);
|
||||
const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun, globalScanData);
|
||||
worldInfoBefore = activatedWorldInfo.worldInfoBefore;
|
||||
worldInfoAfter = activatedWorldInfo.worldInfoAfter;
|
||||
worldInfoString = worldInfoBefore + worldInfoAfter;
|
||||
@@ -966,7 +1010,7 @@ function registerWorldInfoSlashCommands() {
|
||||
/**
|
||||
* Gets the name of the character-bound lorebook.
|
||||
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments
|
||||
* @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} name Character name
|
||||
* @param {string} name Character name
|
||||
* @returns {string} The name of the character-bound lorebook, a JSON string of the character's lorebooks, or an empty string
|
||||
*/
|
||||
function getCharBookCallback({ type }, name) {
|
||||
@@ -1338,6 +1382,18 @@ function registerWorldInfoSlashCommands() {
|
||||
}
|
||||
}
|
||||
|
||||
async function getGlobalBooksCallback() {
|
||||
if (!selected_world_info?.length) {
|
||||
return JSON.stringify([]);
|
||||
}
|
||||
|
||||
let entries = selected_world_info.slice();
|
||||
|
||||
console.debug(`[WI] Selected global world info has ${entries.length} entries`, selected_world_info);
|
||||
|
||||
return JSON.stringify(entries);
|
||||
}
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'world',
|
||||
callback: onWorldInfoChange,
|
||||
@@ -1379,6 +1435,13 @@ function registerWorldInfoSlashCommands() {
|
||||
],
|
||||
aliases: ['getchatlore', 'getchatwi'],
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'getglobalbooks',
|
||||
callback: getGlobalBooksCallback,
|
||||
returns: 'list of selected lorebook names',
|
||||
helpString: 'Get a list of names of the selected global lorebooks and pass it down the pipe.',
|
||||
aliases: ['getgloballore', 'getglobalwi'],
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'getpersonabook',
|
||||
callback: getPersonaBookCallback,
|
||||
@@ -2172,6 +2235,12 @@ export const originalWIDataKeyMap = {
|
||||
'matchWholeWords': 'extensions.match_whole_words',
|
||||
'useGroupScoring': 'extensions.use_group_scoring',
|
||||
'caseSensitive': 'extensions.case_sensitive',
|
||||
'matchPersonaDescription': 'extensions.match_persona_description',
|
||||
'matchCharacterDescription': 'extensions.match_character_description',
|
||||
'matchCharacterPersonality': 'extensions.match_character_personality',
|
||||
'matchCharacterDepthPrompt': 'extensions.match_character_depth_prompt',
|
||||
'matchScenario': 'extensions.match_scenario',
|
||||
'matchCreatorNotes': 'extensions.match_creator_notes',
|
||||
'scanDepth': 'extensions.scan_depth',
|
||||
'automationId': 'extensions.automation_id',
|
||||
'vectorized': 'extensions.vectorized',
|
||||
@@ -2589,7 +2658,7 @@ export async function getWorldEntry(name, data, entry) {
|
||||
if (!isMobile()) {
|
||||
$(characterFilter).select2({
|
||||
width: '100%',
|
||||
placeholder: 'Tie this entry to specific characters or characters with specific tags',
|
||||
placeholder: t`Tie this entry to specific characters or characters with specific tags`,
|
||||
allowClear: true,
|
||||
closeOnSelect: false,
|
||||
});
|
||||
@@ -3189,7 +3258,7 @@ export async function getWorldEntry(name, data, entry) {
|
||||
|
||||
// Create wrapper div
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.textContent = t`Move "${sourceName}" to:`;
|
||||
wrapper.textContent = t`Move '${sourceName}' to:`;
|
||||
|
||||
// Create container and append elements
|
||||
const container = document.createElement('div');
|
||||
@@ -3289,6 +3358,28 @@ export async function getWorldEntry(name, data, entry) {
|
||||
});
|
||||
useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
|
||||
|
||||
function handleMatchCheckbox(fieldName) {
|
||||
const key = originalWIDataKeyMap[fieldName];
|
||||
const checkBoxElem = template.find(`input[type="checkbox"][name="${fieldName}"]`);
|
||||
checkBoxElem.data('uid', entry.uid);
|
||||
checkBoxElem.on('input', async function () {
|
||||
const uid = $(this).data('uid');
|
||||
const value = $(this).prop('checked');
|
||||
|
||||
data.entries[uid][fieldName] = value;
|
||||
setWIOriginalDataValue(data, uid, key, data.entries[uid][fieldName]);
|
||||
await saveWorldInfo(name, data);
|
||||
});
|
||||
checkBoxElem.prop('checked', !!entry[fieldName]).trigger('input');
|
||||
}
|
||||
|
||||
handleMatchCheckbox('matchPersonaDescription');
|
||||
handleMatchCheckbox('matchCharacterDescription');
|
||||
handleMatchCheckbox('matchCharacterPersonality');
|
||||
handleMatchCheckbox('matchCharacterDepthPrompt');
|
||||
handleMatchCheckbox('matchScenario');
|
||||
handleMatchCheckbox('matchCreatorNotes');
|
||||
|
||||
// automation id
|
||||
const automationIdInput = template.find('input[name="automationId"]');
|
||||
automationIdInput.data('uid', entry.uid);
|
||||
@@ -3424,7 +3515,7 @@ function createEntryInputAutocomplete(input, callback, { allowMultiple = false }
|
||||
});
|
||||
|
||||
$(input).on('focus click', function () {
|
||||
$(input).autocomplete('search', allowMultiple ? String($(input).val()).split(/,\s*/).pop() : $(input).val());
|
||||
$(input).autocomplete('search', allowMultiple ? String($(input).val()).split(/,\s*/).pop() : String($(input).val()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3495,6 +3586,12 @@ export const newWorldInfoEntryDefinition = {
|
||||
disable: { default: false, type: 'boolean' },
|
||||
excludeRecursion: { default: false, type: 'boolean' },
|
||||
preventRecursion: { default: false, type: 'boolean' },
|
||||
matchPersonaDescription: { default: false, type: 'boolean' },
|
||||
matchCharacterDescription: { default: false, type: 'boolean' },
|
||||
matchCharacterPersonality: { default: false, type: 'boolean' },
|
||||
matchCharacterDepthPrompt: { default: false, type: 'boolean' },
|
||||
matchScenario: { default: false, type: 'boolean' },
|
||||
matchCreatorNotes: { default: false, type: 'boolean' },
|
||||
delayUntilRecursion: { default: 0, type: 'number' },
|
||||
probability: { default: 100, type: 'number' },
|
||||
useProbability: { default: true, type: 'boolean' },
|
||||
@@ -3959,6 +4056,7 @@ function parseDecorators(content) {
|
||||
* @param {string[]} chat The chat messages to scan, in reverse order.
|
||||
* @param {number} maxContext The maximum context size of the generation.
|
||||
* @param {boolean} isDryRun Whether to perform a dry run.
|
||||
* @param {WIGlobalScanData} globalScanData Chat independent context to be scanned
|
||||
* @typedef {object} WIActivated
|
||||
* @property {string} worldInfoBefore The world info before the chat.
|
||||
* @property {string} worldInfoAfter The world info after the chat.
|
||||
@@ -3969,9 +4067,9 @@ function parseDecorators(content) {
|
||||
* @property {Set<any>} allActivatedEntries All entries.
|
||||
* @returns {Promise<WIActivated>} The world info activated.
|
||||
*/
|
||||
export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
export async function checkWorldInfo(chat, maxContext, isDryRun, globalScanData) {
|
||||
const context = getContext();
|
||||
const buffer = new WorldInfoBuffer(chat);
|
||||
const buffer = new WorldInfoBuffer(chat, globalScanData);
|
||||
|
||||
console.debug(`[WI] --- START WI SCAN (on ${chat.length} messages)${isDryRun ? ' (DRY RUN)' : ''} ---`);
|
||||
|
||||
@@ -4829,6 +4927,12 @@ export function convertCharacterBook(characterBook) {
|
||||
sticky: entry.extensions?.sticky ?? null,
|
||||
cooldown: entry.extensions?.cooldown ?? null,
|
||||
delay: entry.extensions?.delay ?? null,
|
||||
matchPersonaDescription: entry.extensions?.match_persona_description ?? false,
|
||||
matchCharacterDescription: entry.extensions?.match_character_description ?? false,
|
||||
matchCharacterPersonality: entry.extensions?.match_character_personality ?? false,
|
||||
matchCharacterDepthPrompt: entry.extensions?.match_character_depth_prompt ?? false,
|
||||
matchScenario: entry.extensions?.match_scenario ?? false,
|
||||
matchCreatorNotes: entry.extensions?.match_creator_notes ?? false,
|
||||
extensions: entry.extensions ?? {},
|
||||
};
|
||||
});
|
||||
@@ -5099,7 +5203,7 @@ export function openWorldInfoEditor(worldName) {
|
||||
|
||||
/**
|
||||
* Assigns a lorebook to the current chat.
|
||||
* @param {PointerEvent} event Pointer event
|
||||
* @param {JQuery.ClickEvent<Document, undefined, any, any>} event Pointer event
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function assignLorebookToChat(event) {
|
||||
@@ -5138,11 +5242,106 @@ export async function assignLorebookToChat(event) {
|
||||
saveMetadata();
|
||||
});
|
||||
|
||||
return callGenericPopup(template, POPUP_TYPE.TEXT);
|
||||
await callGenericPopup(template, POPUP_TYPE.TEXT);
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
/**
|
||||
* Moves a World Info entry from a source lorebook to a target lorebook.
|
||||
*
|
||||
* @param {string} sourceName - The name of the source lorebook file.
|
||||
* @param {string} targetName - The name of the target lorebook file.
|
||||
* @param {string|number} uid - The UID of the entry to move from the source lorebook.
|
||||
* @returns {Promise<boolean>} True if the move was successful, false otherwise.
|
||||
*/
|
||||
export async function moveWorldInfoEntry(sourceName, targetName, uid) {
|
||||
if (sourceName === targetName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!world_names.includes(sourceName)) {
|
||||
toastr.error(t`Source lorebook '${sourceName}' not found.`);
|
||||
console.error(`[WI Move] Source lorebook '${sourceName}' does not exist.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!world_names.includes(targetName)) {
|
||||
toastr.error(t`Target lorebook '${targetName}' not found.`);
|
||||
console.error(`[WI Move] Target lorebook '${targetName}' does not exist.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const entryUidString = String(uid);
|
||||
|
||||
try {
|
||||
const sourceData = await loadWorldInfo(sourceName);
|
||||
const targetData = await loadWorldInfo(targetName);
|
||||
|
||||
if (!sourceData || !sourceData.entries) {
|
||||
toastr.error(t`Failed to load data for source lorebook '${sourceName}'.`);
|
||||
console.error(`[WI Move] Could not load source data for '${sourceName}'.`);
|
||||
return false;
|
||||
}
|
||||
if (!targetData || !targetData.entries) {
|
||||
toastr.error(t`Failed to load data for target lorebook '${targetName}'.`);
|
||||
console.error(`[WI Move] Could not load target data for '${targetName}'.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sourceData.entries[entryUidString]) {
|
||||
toastr.error(t`Entry not found in source lorebook '${sourceName}'.`);
|
||||
console.error(`[WI Move] Entry UID ${entryUidString} not found in '${sourceName}'.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const entryToMove = structuredClone(sourceData.entries[entryUidString]);
|
||||
|
||||
|
||||
const newUid = getFreeWorldEntryUid(targetData);
|
||||
if (newUid === null) {
|
||||
console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
entryToMove.uid = newUid;
|
||||
// Place the entry at the end of the target lorebook
|
||||
const maxDisplayIndex = Object.values(targetData.entries).reduce((max, entry) => Math.max(max, entry.displayIndex ?? -1), -1);
|
||||
entryToMove.displayIndex = maxDisplayIndex + 1;
|
||||
|
||||
targetData.entries[newUid] = entryToMove;
|
||||
|
||||
delete sourceData.entries[entryUidString];
|
||||
// Remove from originalData if it exists
|
||||
deleteWIOriginalDataValue(sourceData, entryUidString);
|
||||
// TODO: setWIOriginalDataValue
|
||||
console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`);
|
||||
|
||||
|
||||
await saveWorldInfo(targetName, targetData, true);
|
||||
console.debug(`[WI Move] Saved target lorebook '${targetName}'.`);
|
||||
await saveWorldInfo(sourceName, sourceData, true);
|
||||
console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`);
|
||||
|
||||
|
||||
console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`);
|
||||
|
||||
// Check if the currently viewed book in the editor is the source or target and reload it
|
||||
const currentEditorBookIndex = Number($('#world_editor_select').val());
|
||||
if (!isNaN(currentEditorBookIndex)) {
|
||||
const currentEditorBookName = world_names[currentEditorBookIndex];
|
||||
if (currentEditorBookName === sourceName || currentEditorBookName === targetName) {
|
||||
reloadEditor(currentEditorBookName);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`);
|
||||
console.error('[WI Move] Unexpected error:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function initWorldInfo() {
|
||||
$('#world_info').on('mousedown change', async function (e) {
|
||||
// If there's no world names, don't do anything
|
||||
if (world_names.length === 0) {
|
||||
@@ -5329,7 +5528,7 @@ jQuery(() => {
|
||||
if (!isMobile()) {
|
||||
$('#world_info').select2({
|
||||
width: '100%',
|
||||
placeholder: 'No Worlds active. Click here to select.',
|
||||
placeholder: t`No Worlds active. Click here to select.`,
|
||||
allowClear: true,
|
||||
closeOnSelect: false,
|
||||
});
|
||||
@@ -5354,100 +5553,4 @@ jQuery(() => {
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Moves a World Info entry from a source lorebook to a target lorebook.
|
||||
*
|
||||
* @param {string} sourceName - The name of the source lorebook file.
|
||||
* @param {string} targetName - The name of the target lorebook file.
|
||||
* @param {string|number} uid - The UID of the entry to move from the source lorebook.
|
||||
* @returns {Promise<boolean>} True if the move was successful, false otherwise.
|
||||
*/
|
||||
export async function moveWorldInfoEntry(sourceName, targetName, uid) {
|
||||
if (sourceName === targetName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!world_names.includes(sourceName)) {
|
||||
toastr.error(t`Source lorebook '${sourceName}' not found.`);
|
||||
console.error(`[WI Move] Source lorebook '${sourceName}' does not exist.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!world_names.includes(targetName)) {
|
||||
toastr.error(t`Target lorebook '${targetName}' not found.`);
|
||||
console.error(`[WI Move] Target lorebook '${targetName}' does not exist.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const entryUidString = String(uid);
|
||||
|
||||
try {
|
||||
const sourceData = await loadWorldInfo(sourceName);
|
||||
const targetData = await loadWorldInfo(targetName);
|
||||
|
||||
if (!sourceData || !sourceData.entries) {
|
||||
toastr.error(t`Failed to load data for source lorebook '${sourceName}'.`);
|
||||
console.error(`[WI Move] Could not load source data for '${sourceName}'.`);
|
||||
return false;
|
||||
}
|
||||
if (!targetData || !targetData.entries) {
|
||||
toastr.error(t`Failed to load data for target lorebook '${targetName}'.`);
|
||||
console.error(`[WI Move] Could not load target data for '${targetName}'.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sourceData.entries[entryUidString]) {
|
||||
toastr.error(t`Entry not found in source lorebook '${sourceName}'.`);
|
||||
console.error(`[WI Move] Entry UID ${entryUidString} not found in '${sourceName}'.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const entryToMove = structuredClone(sourceData.entries[entryUidString]);
|
||||
|
||||
|
||||
const newUid = getFreeWorldEntryUid(targetData);
|
||||
if (newUid === null) {
|
||||
console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
entryToMove.uid = newUid;
|
||||
// Place the entry at the end of the target lorebook
|
||||
const maxDisplayIndex = Object.values(targetData.entries).reduce((max, entry) => Math.max(max, entry.displayIndex ?? -1), -1);
|
||||
entryToMove.displayIndex = maxDisplayIndex + 1;
|
||||
|
||||
targetData.entries[newUid] = entryToMove;
|
||||
|
||||
delete sourceData.entries[entryUidString];
|
||||
// Remove from originalData if it exists
|
||||
deleteWIOriginalDataValue(sourceData, entryUidString);
|
||||
// TODO: setWIOriginalDataValue
|
||||
console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`);
|
||||
|
||||
|
||||
await saveWorldInfo(targetName, targetData, true);
|
||||
console.debug(`[WI Move] Saved target lorebook '${targetName}'.`);
|
||||
await saveWorldInfo(sourceName, sourceData, true);
|
||||
console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`);
|
||||
|
||||
|
||||
console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`);
|
||||
|
||||
// Check if the currently viewed book in the editor is the source or target and reload it
|
||||
const currentEditorBookIndex = Number($('#world_editor_select').val());
|
||||
if (!isNaN(currentEditorBookIndex)) {
|
||||
const currentEditorBookName = world_names[currentEditorBookIndex];
|
||||
if (currentEditorBookName === sourceName || currentEditorBookName === targetName) {
|
||||
reloadEditor(currentEditorBookName);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`);
|
||||
console.error('[WI Move] Unexpected error:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user