mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into support-multiple-expressions
This commit is contained in:
@@ -11,6 +11,7 @@ import { debounce_timeout } from './constants.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { Popup } from './popup.js';
|
||||
import { t } from './i18n.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
|
||||
function debouncePromise(func, delay) {
|
||||
let timeoutId;
|
||||
@@ -1562,6 +1563,7 @@ class PromptManager {
|
||||
|
||||
listItemHtml += `
|
||||
<li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass} ${importantClass}" data-pm-identifier="${escapeHtml(prompt.identifier)}">
|
||||
<span class="drag-handle">☰</span>
|
||||
<span class="${prefix}prompt_manager_prompt_name" data-pm-name="${encodedName}">
|
||||
${isMarkerPrompt ? '<span class="fa-fw fa-solid fa-thumb-tack" title="Marker"></span>' : ''}
|
||||
${isSystemPrompt ? '<span class="fa-fw fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''}
|
||||
@@ -1741,6 +1743,7 @@ class PromptManager {
|
||||
makeDraggable() {
|
||||
$(`#${this.configuration.prefix}prompt_manager_list`).sortable({
|
||||
delay: this.configuration.sortableDelay,
|
||||
handle: isMobile() ? '.drag-handle' : null,
|
||||
items: `.${this.configuration.prefix}prompt_manager_prompt_draggable`,
|
||||
update: (event, ui) => {
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
|
@@ -59,6 +59,16 @@ const hash_derivations = {
|
||||
// Tulu-3-8B
|
||||
// Tulu-3-70B
|
||||
'Tulu'
|
||||
,
|
||||
|
||||
// DeepSeek V2.5
|
||||
'54d400beedcd17f464e10063e0577f6f798fa896266a912d8a366f8a2fcc0bca':
|
||||
'DeepSeek-V2.5'
|
||||
,
|
||||
|
||||
// DeepSeek R1
|
||||
'b6835114b7303ddd78919a82e4d9f7d8c26ed0d7dfc36beeb12d524f6144eab1':
|
||||
'DeepSeek-V2.5'
|
||||
};
|
||||
|
||||
const substr_derivations = {
|
||||
|
@@ -11,6 +11,7 @@ import {
|
||||
getCurrentChatId,
|
||||
getRequestHeaders,
|
||||
hideSwipeButtons,
|
||||
name1,
|
||||
name2,
|
||||
reloadCurrentChat,
|
||||
saveChatDebounced,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
chat_metadata,
|
||||
neutralCharacterName,
|
||||
updateChatMetadata,
|
||||
system_message_types,
|
||||
} from '../script.js';
|
||||
import { selected_group } from './group-chats.js';
|
||||
import { power_user } from './power-user.js';
|
||||
@@ -34,6 +36,7 @@ import {
|
||||
humanFileSize,
|
||||
saveBase64AsFile,
|
||||
extractTextFromOffice,
|
||||
download,
|
||||
} from './utils.js';
|
||||
import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
@@ -41,6 +44,7 @@ import { ScraperManager } from './scrapers.js';
|
||||
import { DragAndDropHandler } from './dragdrop.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { t } from './i18n.js';
|
||||
import { humanizedDateTime } from './RossAscends-mods.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileAttachment
|
||||
@@ -585,10 +589,12 @@ async function enlargeMessageImage() {
|
||||
const imgHolder = document.createElement('div');
|
||||
imgHolder.classList.add('img_enlarged_holder');
|
||||
imgHolder.append(img);
|
||||
const imgContainer = $('<div><pre><code></code></pre></div>');
|
||||
const imgContainer = $('<div><pre><code class="img_enlarged_title"></code></pre></div>');
|
||||
imgContainer.prepend(imgHolder);
|
||||
imgContainer.addClass('img_enlarged_container');
|
||||
imgContainer.find('code').addClass('txt').text(title);
|
||||
|
||||
const codeTitle = imgContainer.find('.img_enlarged_title');
|
||||
codeTitle.addClass('txt').text(title);
|
||||
const titleEmpty = !title || title.trim().length === 0;
|
||||
imgContainer.find('pre').toggle(!titleEmpty);
|
||||
addCopyToCodeBlocks(imgContainer);
|
||||
@@ -598,9 +604,17 @@ async function enlargeMessageImage() {
|
||||
popup.dlg.style.width = 'unset';
|
||||
popup.dlg.style.height = 'unset';
|
||||
|
||||
img.addEventListener('click', () => {
|
||||
img.addEventListener('click', event => {
|
||||
const shouldZoom = !img.classList.contains('zoomed');
|
||||
img.classList.toggle('zoomed', shouldZoom);
|
||||
event.stopPropagation();
|
||||
});
|
||||
codeTitle[0]?.addEventListener('click', event => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
popup.dlg.addEventListener('click', event => {
|
||||
popup.completeCancelled();
|
||||
});
|
||||
|
||||
await popup.show();
|
||||
@@ -1427,6 +1441,19 @@ jQuery(function () {
|
||||
await viewMessageFile(messageId);
|
||||
});
|
||||
|
||||
$(document).on('click', '.assistant_note_export', async function () {
|
||||
const chatToSave = [
|
||||
{
|
||||
user_name: name1,
|
||||
character_name: name2,
|
||||
chat_metadata: chat_metadata,
|
||||
},
|
||||
...chat.filter(x => x?.extra?.type !== system_message_types.ASSISTANT_NOTE),
|
||||
];
|
||||
|
||||
download(JSON.stringify(chatToSave, null, 4), `Assistant - ${humanizedDateTime()}.json`, 'application/json');
|
||||
});
|
||||
|
||||
// Do not change. #attachFile is added by extension.
|
||||
$(document).on('click', '#attachFile', function () {
|
||||
$('#file_form_input').trigger('click');
|
||||
|
@@ -4,7 +4,7 @@ import { eventSource, event_types, saveSettings, saveSettingsDebounced, getReque
|
||||
import { showLoader } from './loader.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { renderTemplate, renderTemplateAsync } from './templates.js';
|
||||
import { delay, isSubsetOf, setValueByPath } from './utils.js';
|
||||
import { delay, isSubsetOf, sanitizeSelector, setValueByPath } from './utils.js';
|
||||
import { getContext } from './st-context.js';
|
||||
import { isAdmin } from './user.js';
|
||||
import { t } from './i18n.js';
|
||||
@@ -38,7 +38,8 @@ export let modules = [];
|
||||
let activeExtensions = new Set();
|
||||
|
||||
const getApiUrl = () => extension_settings.apiUrl;
|
||||
const sortManifests = (a, b) => parseInt(a.loading_order) - parseInt(b.loading_order) || String(a.display_name).localeCompare(String(b.display_name));
|
||||
const sortManifestsByOrder = (a, b) => parseInt(a.loading_order) - parseInt(b.loading_order) || String(a.display_name).localeCompare(String(b.display_name));
|
||||
const sortManifestsByName = (a, b) => String(a.display_name).localeCompare(String(b.display_name)) || parseInt(a.loading_order) - parseInt(b.loading_order);
|
||||
let connectedToApi = false;
|
||||
|
||||
/**
|
||||
@@ -365,7 +366,7 @@ async function getManifests(names) {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function activateExtensions() {
|
||||
const extensions = Object.entries(manifests).sort((a, b) => sortManifests(a[1], b[1]));
|
||||
const extensions = Object.entries(manifests).sort((a, b) => sortManifestsByOrder(a[1], b[1]));
|
||||
const promises = [];
|
||||
|
||||
for (let entry of extensions) {
|
||||
@@ -519,10 +520,11 @@ function addExtensionStyle(name, manifest) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = `/scripts/extensions/${name}/${manifest.css}`;
|
||||
const id = sanitizeSelector(`${name}-css`);
|
||||
|
||||
if ($(`link[id="${name}"]`).length === 0) {
|
||||
if ($(`link[id="${id}"]`).length === 0) {
|
||||
const link = document.createElement('link');
|
||||
link.id = name;
|
||||
link.id = id;
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = url;
|
||||
@@ -550,11 +552,12 @@ function addExtensionScript(name, manifest) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = `/scripts/extensions/${name}/${manifest.js}`;
|
||||
const id = sanitizeSelector(`${name}-js`);
|
||||
let ready = false;
|
||||
|
||||
if ($(`script[id="${name}"]`).length === 0) {
|
||||
if ($(`script[id="${id}"]`).length === 0) {
|
||||
const script = document.createElement('script');
|
||||
script.id = name;
|
||||
script.id = id;
|
||||
script.type = 'module';
|
||||
script.src = url;
|
||||
script.async = true;
|
||||
@@ -690,9 +693,9 @@ function getExtensionData(extension) {
|
||||
* @return {string} - The HTML string for the module information.
|
||||
*/
|
||||
function getModuleInformation() {
|
||||
let moduleInfo = modules.length ? `<p>${DOMPurify.sanitize(modules.join(', '))}</p>` : '<p class="failure">Not connected to the API!</p>';
|
||||
let moduleInfo = modules.length ? `<p>${DOMPurify.sanitize(modules.join(', '))}</p>` : '<p class="failure">' + t`Not connected to the API!` + '</p>';
|
||||
return `
|
||||
<h3>Modules provided by your Extras API:</h3>
|
||||
<h3>` + t`Modules provided by your Extras API:` + `</h3>
|
||||
${moduleInfo}
|
||||
`;
|
||||
}
|
||||
@@ -711,16 +714,19 @@ async function showExtensionsDetails() {
|
||||
initialScrollTop = oldPopup.content.scrollTop;
|
||||
await oldPopup.completeCancelled();
|
||||
}
|
||||
const htmlDefault = $('<div class="marginBot10"><h3 class="textAlignCenter">Built-in Extensions:</h3></div>');
|
||||
const htmlExternal = $('<div class="marginBot10"><h3 class="textAlignCenter">Installed Extensions:</h3></div>');
|
||||
const htmlDefault = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Built-in Extensions:` + '</h3></div>');
|
||||
const htmlExternal = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Installed Extensions:` + '</h3></div>');
|
||||
const htmlLoading = $(`<div class="flex-container alignItemsCenter justifyCenter marginTop10 marginBot5">
|
||||
<i class="fa-solid fa-spinner fa-spin"></i>
|
||||
<span>Loading third-party extensions... Please wait...</span>
|
||||
<span>` + t`Loading third-party extensions... Please wait...` + `</span>
|
||||
</div>`);
|
||||
|
||||
htmlExternal.append(htmlLoading);
|
||||
|
||||
const extensions = Object.entries(manifests).sort((a, b) => sortManifests(a[1], b[1])).map(getExtensionData);
|
||||
const sortOrderKey = 'extensions_sortByName';
|
||||
const sortByName = localStorage.getItem(sortOrderKey) === 'true';
|
||||
const sortFn = sortByName ? sortManifestsByName : sortManifestsByOrder;
|
||||
const extensions = Object.entries(manifests).sort((a, b) => sortFn(a[1], b[1])).map(getExtensionData);
|
||||
|
||||
extensions.forEach(value => {
|
||||
const { isExternal, extensionHtml } = value;
|
||||
@@ -736,8 +742,7 @@ async function showExtensionsDetails() {
|
||||
|
||||
/** @type {import('./popup.js').CustomPopupButton} */
|
||||
const updateAllButton = {
|
||||
text: 'Update all',
|
||||
appendAtEnd: true,
|
||||
text: t`Update all`,
|
||||
action: async () => {
|
||||
requiresReload = true;
|
||||
await autoUpdateExtensions(true);
|
||||
@@ -745,13 +750,23 @@ async function showExtensionsDetails() {
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('./popup.js').CustomPopupButton} */
|
||||
const sortOrderButton = {
|
||||
text: sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`,
|
||||
action: async () => {
|
||||
abortController.abort();
|
||||
localStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true');
|
||||
await showExtensionsDetails();
|
||||
},
|
||||
};
|
||||
|
||||
let waitingForSave = false;
|
||||
|
||||
const popup = new Popup(html, POPUP_TYPE.TEXT, '', {
|
||||
okButton: 'Close',
|
||||
okButton: t`Close`,
|
||||
wide: true,
|
||||
large: true,
|
||||
customButtons: [updateAllButton],
|
||||
customButtons: [sortOrderButton, updateAllButton],
|
||||
allowVerticalScrolling: true,
|
||||
onClosing: async () => {
|
||||
if (waitingForSave) {
|
||||
@@ -770,7 +785,7 @@ async function showExtensionsDetails() {
|
||||
});
|
||||
popupPromise = popup.show();
|
||||
popup.content.scrollTop = initialScrollTop;
|
||||
checkForUpdatesManual(abortController.signal).finally(() => htmlLoading.remove());
|
||||
checkForUpdatesManual(sortFn, abortController.signal).finally(() => htmlLoading.remove());
|
||||
} catch (error) {
|
||||
toastr.error(t`Error loading extensions. See browser console for details.`);
|
||||
console.error(error);
|
||||
@@ -841,7 +856,7 @@ async function updateExtension(extensionName, quiet) {
|
||||
toastr.success('Extension is already up to date');
|
||||
}
|
||||
} else {
|
||||
toastr.success(`Extension ${extensionName} updated to ${data.shortCommitHash}`, 'Reload the page to apply updates');
|
||||
toastr.success(t`Extension ${extensionName} updated to ${data.shortCommitHash}`, t`Reload the page to apply updates`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
@@ -1009,7 +1024,7 @@ export async function installExtension(url, global) {
|
||||
}
|
||||
|
||||
const response = await request.json();
|
||||
toastr.success(`Extension "${response.display_name}" by ${response.author} (version ${response.version}) has been installed successfully!`, 'Extension installation successful');
|
||||
toastr.success(t`Extension '${response.display_name}' by ${response.author} (version ${response.version}) has been installed successfully!`, t`Extension installation successful`);
|
||||
console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`);
|
||||
await loadExtensionSettings({}, false, false);
|
||||
await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
|
||||
@@ -1081,12 +1096,13 @@ function processVersionCheckQueue() {
|
||||
|
||||
/**
|
||||
* Performs a manual check for updates on all 3rd-party extensions.
|
||||
* @param {function} sortFn Sort function
|
||||
* @param {AbortSignal} abortSignal Signal to abort the operation
|
||||
* @returns {Promise<any[]>}
|
||||
*/
|
||||
async function checkForUpdatesManual(abortSignal) {
|
||||
async function checkForUpdatesManual(sortFn, abortSignal) {
|
||||
const promises = [];
|
||||
for (const id of Object.keys(manifests).filter(x => x.startsWith('third-party')).sort((a, b) => sortManifests(manifests[a], manifests[b]))) {
|
||||
for (const id of Object.keys(manifests).filter(x => x.startsWith('third-party')).sort((a, b) => sortFn(manifests[a], manifests[b]))) {
|
||||
const externalId = id.replace('third-party', '');
|
||||
const promise = enqueueVersionCheck(async () => {
|
||||
try {
|
||||
@@ -1183,7 +1199,7 @@ async function checkForExtensionUpdates(force) {
|
||||
await Promise.allSettled(promises);
|
||||
|
||||
if (updatesAvailable.length > 0) {
|
||||
toastr.info(`${updatesAvailable.map(x => `• ${x}`).join('\n')}`, 'Extension updates available');
|
||||
toastr.info(`${updatesAvailable.map(x => `• ${x}`).join('\n')}`, t`Extension updates available`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1197,7 +1213,7 @@ async function autoUpdateExtensions(forceAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
const banner = toastr.info('Auto-updating extensions. This may take several minutes.', 'Please wait...', { timeOut: 10000, extendedTimeOut: 10000 });
|
||||
const banner = toastr.info(t`Auto-updating extensions. This may take several minutes.`, t`Please wait...`, { timeOut: 10000, extendedTimeOut: 10000 });
|
||||
const isCurrentUserAdmin = isAdmin();
|
||||
const promises = [];
|
||||
for (const [id, manifest] of Object.entries(manifests)) {
|
||||
@@ -1231,7 +1247,7 @@ export async function runGenerationInterceptors(chat, contextSize, type) {
|
||||
exitImmediately = immediately;
|
||||
};
|
||||
|
||||
for (const manifest of Object.values(manifests).filter(x => x.generate_interceptor).sort((a, b) => sortManifests(a, b))) {
|
||||
for (const manifest of Object.values(manifests).filter(x => x.generate_interceptor).sort((a, b) => sortManifestsByOrder(a, b))) {
|
||||
const interceptorKey = manifest.generate_interceptor;
|
||||
if (typeof globalThis[interceptorKey] === 'function') {
|
||||
try {
|
||||
|
@@ -54,6 +54,8 @@
|
||||
<option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
|
||||
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</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-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>
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<div>
|
||||
<h3>Included settings:</h3>
|
||||
<h3 data-i18n="Included settings:">Included settings:</h3>
|
||||
<div class="justifyLeft flex-container flexFlowColumn flexNoGap">
|
||||
{{#each settings}}
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" value="{{@key}}" name="exclude"{{#if this}} checked{{/if}}>
|
||||
<span>{{@key}}</span>
|
||||
<span data-i18n="{{@key}}">{{@key}}</span>
|
||||
</label>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
@@ -12,8 +12,8 @@
|
||||
</div>
|
||||
<div class="marginTop5">
|
||||
<small>
|
||||
<b>Hint:</b>
|
||||
<i>Click on the setting name to omit it from the profile.</i>
|
||||
<b data-i18n="Hint:">Hint:</b>
|
||||
<i data-i18n="Click on the setting name to omit it from the profile.">Click on the setting name to omit it from the profile.</i>
|
||||
</small>
|
||||
</div>
|
||||
<h3 data-i18n="Enter a name:">
|
||||
|
@@ -277,7 +277,7 @@ function makeMovable(id = 'gallery') {
|
||||
const newElement = $(template);
|
||||
newElement.css('background-color', 'var(--SmartThemeBlurTintColor)');
|
||||
newElement.attr('forChar', id);
|
||||
newElement.attr('id', `${id}`);
|
||||
newElement.attr('id', id);
|
||||
newElement.find('.drag-grabber').attr('id', `${id}header`);
|
||||
newElement.find('.dragTitle').text('Image Gallery');
|
||||
//add a div for the gallery
|
||||
|
@@ -46,10 +46,12 @@
|
||||
<div class="qr--title" data-i18n="Edit Quick Replies">Edit Quick Replies</div>
|
||||
<div class="qr--actions">
|
||||
<select id="qr--set" class="text_pole"></select>
|
||||
<div class="qr--add menu_button menu_button_icon fa-solid fa-pencil" id="qr--set-rename" title="Rename quick reply set"></div>
|
||||
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-new" title="Create new quick reply set"></div>
|
||||
<div class="qr--add menu_button menu_button_icon fa-solid fa-file-import" id="qr--set-import" title="Import quick reply set"></div>
|
||||
<input type="file" id="qr--set-importFile" accept=".json" hidden>
|
||||
<div class="qr--add menu_button menu_button_icon fa-solid fa-file-export" id="qr--set-export" title="Export quick reply set"></div>
|
||||
<div class="qr-add menu_button menu_button_icon fa-solid fa-paste" id="qr--set-duplicate" title="Duplicate quick reply set"></div>
|
||||
<div class="qr--del menu_button menu_button_icon fa-solid fa-trash redWarningBG" id="qr--set-delete" title="Delete quick reply set"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { callPopup } from '../../../../../script.js';
|
||||
import { Popup } from '../../../../popup.js';
|
||||
import { getSortableDelay } from '../../../../utils.js';
|
||||
import { log, warn } from '../../index.js';
|
||||
import { QuickReply } from '../QuickReply.js';
|
||||
@@ -111,6 +111,7 @@ export class SettingsUi {
|
||||
|
||||
prepareQrEditor() {
|
||||
// qr editor
|
||||
this.dom.querySelector('#qr--set-rename').addEventListener('click', async () => this.renameQrSet());
|
||||
this.dom.querySelector('#qr--set-new').addEventListener('click', async()=>this.addQrSet());
|
||||
/**@type {HTMLInputElement}*/
|
||||
const importFile = this.dom.querySelector('#qr--set-importFile');
|
||||
@@ -119,7 +120,8 @@ export class SettingsUi {
|
||||
importFile.value = null;
|
||||
});
|
||||
this.dom.querySelector('#qr--set-import').addEventListener('click', ()=>importFile.click());
|
||||
this.dom.querySelector('#qr--set-export').addEventListener('click', async()=>this.exportQrSet());
|
||||
this.dom.querySelector('#qr--set-export').addEventListener('click', async () => this.exportQrSet());
|
||||
this.dom.querySelector('#qr--set-duplicate').addEventListener('click', async () => this.duplicateQrSet());
|
||||
this.dom.querySelector('#qr--set-delete').addEventListener('click', async()=>this.deleteQrSet());
|
||||
this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{
|
||||
this.currentQrSet.addQuickReply();
|
||||
@@ -279,7 +281,7 @@ export class SettingsUi {
|
||||
}
|
||||
|
||||
async deleteQrSet() {
|
||||
const confirmed = await callPopup(`Are you sure you want to delete the Quick Reply Set "${this.currentQrSet.name}"?<br>This cannot be undone.`, 'confirm');
|
||||
const confirmed = await Popup.show.confirm('Delete Quick Reply Set', `Are you sure you want to delete the Quick Reply Set "${this.currentQrSet.name}"?<br>This cannot be undone.`);
|
||||
if (confirmed) {
|
||||
await this.doDeleteQrSet(this.currentQrSet);
|
||||
this.rerender();
|
||||
@@ -303,12 +305,52 @@ export class SettingsUi {
|
||||
this.settings.save();
|
||||
}
|
||||
|
||||
async renameQrSet() {
|
||||
const newName = await Popup.show.input('Rename Quick Reply Set', 'Enter a new name:', this.currentQrSet.name);
|
||||
if (newName && newName.length > 0) {
|
||||
const existingSet = QuickReplySet.get(newName);
|
||||
if (existingSet) {
|
||||
toastr.error(`A Quick Reply Set named "${newName}" already exists.`);
|
||||
return;
|
||||
}
|
||||
const oldName = this.currentQrSet.name;
|
||||
this.currentQrSet.name = newName;
|
||||
await this.currentQrSet.save();
|
||||
|
||||
// Update it in both set lists
|
||||
this.settings.config.setList.forEach(set => {
|
||||
if (set.set.name === oldName) {
|
||||
set.set.name = newName;
|
||||
}
|
||||
});
|
||||
this.settings.chatConfig?.setList.forEach(set => {
|
||||
if (set.set.name === oldName) {
|
||||
set.set.name = newName;
|
||||
}
|
||||
});
|
||||
this.settings.save();
|
||||
|
||||
// Update the option in the current selected QR dropdown. All others will be refreshed via the prepare calls below.
|
||||
/** @type {HTMLOptionElement} */
|
||||
const option = this.currentSet.querySelector(`#qr--set option[value="${oldName}"]`);
|
||||
option.value = newName;
|
||||
option.textContent = newName;
|
||||
|
||||
this.currentSet.value = newName;
|
||||
this.onQrSetChange();
|
||||
this.prepareGlobalSetList();
|
||||
this.prepareChatSetList();
|
||||
|
||||
console.info(`Quick Reply Set renamed from ""${oldName}" to "${newName}".`);
|
||||
}
|
||||
}
|
||||
|
||||
async addQrSet() {
|
||||
const name = await callPopup('Quick Reply Set Name:', 'input');
|
||||
const name = await Popup.show.input('Create a new World Info', 'Enter a name for the new Quick Reply Set:');
|
||||
if (name && name.length > 0) {
|
||||
const oldQrs = QuickReplySet.get(name);
|
||||
if (oldQrs) {
|
||||
const replace = await callPopup(`A Quick Reply Set named "${name}" already exists.<br>Do you want to overwrite the existing Quick Reply Set?<br>The existing set will be deleted. This cannot be undone.`, 'confirm');
|
||||
const replace = Popup.show.confirm('Replace existing World Info', `A Quick Reply Set named "${name}" already exists.<br>Do you want to overwrite the existing Quick Reply Set?<br>The existing set will be deleted. This cannot be undone.`);
|
||||
if (replace) {
|
||||
const idx = QuickReplySet.list.indexOf(oldQrs);
|
||||
await this.doDeleteQrSet(oldQrs);
|
||||
@@ -369,7 +411,7 @@ export class SettingsUi {
|
||||
qrs.init();
|
||||
const oldQrs = QuickReplySet.get(props.name);
|
||||
if (oldQrs) {
|
||||
const replace = await callPopup(`A Quick Reply Set named "${qrs.name}" already exists.<br>Do you want to overwrite the existing Quick Reply Set?<br>The existing set will be deleted. This cannot be undone.`, 'confirm');
|
||||
const replace = Popup.show.confirm('Replace existing World Info', `A Quick Reply Set named "${name}" already exists.<br>Do you want to overwrite the existing Quick Reply Set?<br>The existing set will be deleted. This cannot be undone.`);
|
||||
if (replace) {
|
||||
const idx = QuickReplySet.list.indexOf(oldQrs);
|
||||
await this.doDeleteQrSet(oldQrs);
|
||||
@@ -421,6 +463,40 @@ export class SettingsUi {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
async duplicateQrSet() {
|
||||
const newName = await Popup.show.input('Duplicate Quick Reply Set', 'Enter a name for the new Quick Reply Set:', `${this.currentQrSet.name} (Copy)`);
|
||||
if (newName && newName.length > 0) {
|
||||
const existingSet = QuickReplySet.get(newName);
|
||||
if (existingSet) {
|
||||
toastr.error(`A Quick Reply Set named "${newName}" already exists.`);
|
||||
return;
|
||||
}
|
||||
const newQrSet = QuickReplySet.from(JSON.parse(JSON.stringify(this.currentQrSet)));
|
||||
newQrSet.name = newName;
|
||||
newQrSet.qrList = this.currentQrSet.qrList.map(qr => QuickReply.from(JSON.parse(JSON.stringify(qr))));
|
||||
newQrSet.addQuickReply();
|
||||
const idx = QuickReplySet.list.findIndex(it => it.name.toLowerCase().localeCompare(newName.toLowerCase()) == 1);
|
||||
if (idx > -1) {
|
||||
QuickReplySet.list.splice(idx, 0, newQrSet);
|
||||
} else {
|
||||
QuickReplySet.list.push(newQrSet);
|
||||
}
|
||||
const opt = document.createElement('option'); {
|
||||
opt.value = newQrSet.name;
|
||||
opt.textContent = newQrSet.name;
|
||||
if (idx > -1) {
|
||||
this.currentSet.children[idx].insertAdjacentElement('beforebegin', opt);
|
||||
} else {
|
||||
this.currentSet.append(opt);
|
||||
}
|
||||
}
|
||||
this.currentSet.value = newName;
|
||||
this.onQrSetChange();
|
||||
this.prepareGlobalSetList();
|
||||
this.prepareChatSetList();
|
||||
}
|
||||
}
|
||||
|
||||
selectQrSet(qrs) {
|
||||
this.currentSet.value = qrs.name;
|
||||
this.onQrSetChange();
|
||||
|
@@ -388,7 +388,7 @@ class AllTalkTtsProvider {
|
||||
}
|
||||
|
||||
async fetchRvcVoiceObjects() {
|
||||
if (this.settings.server_version !== 'v2') {
|
||||
if (this.settings.server_version == 'v2') {
|
||||
console.log('Skipping RVC voices fetch for V1 server');
|
||||
return [];
|
||||
}
|
||||
|
@@ -30,6 +30,8 @@ import { textgen_types, textgenerationwebui_settings } from '../../textgen-setti
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js';
|
||||
import { callGenericPopup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';
|
||||
import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';
|
||||
|
||||
@@ -1613,25 +1615,55 @@ jQuery(async () => {
|
||||
callback: async (args, query) => {
|
||||
const clamp = (v) => Number.isNaN(v) ? null : Math.min(1, Math.max(0, v));
|
||||
const threshold = clamp(Number(args?.threshold ?? settings.score_threshold));
|
||||
const validateCount = (v) => Number.isNaN(v) || !Number.isInteger(v) || v < 1 ? null : v;
|
||||
const count = validateCount(Number(args?.count)) ?? settings.chunk_count_db;
|
||||
const source = String(args?.source ?? '');
|
||||
const attachments = source ? getDataBankAttachmentsForSource(source, false) : getDataBankAttachments(false);
|
||||
const collectionIds = await ingestDataBankAttachments(String(source));
|
||||
const queryResults = await queryMultipleCollections(collectionIds, String(query), settings.chunk_count_db, threshold);
|
||||
|
||||
// Map collection IDs to file URLs
|
||||
const queryResults = await queryMultipleCollections(collectionIds, String(query), count, threshold);
|
||||
|
||||
// Get URLs
|
||||
const urls = Object
|
||||
.keys(queryResults)
|
||||
.map(x => attachments.find(y => getFileCollectionId(y.url) === x))
|
||||
.filter(x => x)
|
||||
.map(x => x.url);
|
||||
|
||||
// Gets the actual text content of chunks
|
||||
const getChunksText = () => {
|
||||
let textResult = '';
|
||||
for (const collectionId in queryResults) {
|
||||
const metadata = queryResults[collectionId].metadata?.filter(x => x.text)?.sort((a, b) => a.index - b.index)?.map(x => x.text)?.filter(onlyUnique) || [];
|
||||
textResult += metadata.join('\n') + '\n\n';
|
||||
}
|
||||
return textResult;
|
||||
};
|
||||
|
||||
if (args.return === 'chunks') {
|
||||
return getChunksText();
|
||||
}
|
||||
|
||||
return JSON.stringify(urls);
|
||||
// @ts-ignore
|
||||
return slashCommandReturnHelper.doReturn(args.return ?? 'object', urls, { objectToStringFunc: list => list.join('\n') });
|
||||
|
||||
},
|
||||
aliases: ['databank-search', 'data-bank-search'],
|
||||
helpString: 'Search the Data Bank for a specific query using vector similarity. Returns a list of file URLs with the most relevant content.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('threshold', 'Threshold for the similarity score in the [0, 1] range. Uses the global config value if not set.', ARGUMENT_TYPE.NUMBER, false, false, ''),
|
||||
new SlashCommandNamedArgument('count', 'Maximum number of query results to return.', ARGUMENT_TYPE.NUMBER, false, false, ''),
|
||||
new SlashCommandNamedArgument('source', 'Optional filter for the attachments by source.', ARGUMENT_TYPE.STRING, false, false, '', ['global', 'character', 'chat']),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'return',
|
||||
description: 'How you want the return value to be provided',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'object',
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('chunks', 'Return the actual content chunks', enumTypes.enum, '{}'),
|
||||
...slashCommandReturnHelper.enumList({ allowObject: true })
|
||||
],
|
||||
forceEnum: true,
|
||||
})
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('Query to search by.', ARGUMENT_TYPE.STRING, true, false),
|
||||
|
@@ -81,6 +81,7 @@ import { t } from './i18n.js';
|
||||
|
||||
export {
|
||||
selected_group,
|
||||
openGroupId,
|
||||
is_group_automode_enabled,
|
||||
hideMutedSprites,
|
||||
is_group_generating,
|
||||
@@ -1367,6 +1368,15 @@ function getGroupCharacterBlock(character) {
|
||||
template.find('.ch_fav').val(isFav);
|
||||
template.toggleClass('is_fav', isFav);
|
||||
|
||||
const auxFieldName = power_user.aux_field || 'character_version';
|
||||
const auxFieldValue = (character.data && character.data[auxFieldName]) || '';
|
||||
if (auxFieldValue) {
|
||||
template.find('.character_version').text(auxFieldValue);
|
||||
}
|
||||
else {
|
||||
template.find('.character_version').hide();
|
||||
}
|
||||
|
||||
let queuePosition = groupChatQueueOrder.get(character.avatar);
|
||||
if (queuePosition) {
|
||||
template.find('.queue_position').text(queuePosition);
|
||||
|
@@ -26,6 +26,12 @@ Handlebars.registerHelper('helperMissing', function () {
|
||||
* @typedef {(nonce: string) => string} MacroFunction
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} CustomMacro
|
||||
* @property {string} key - Macro name (key)
|
||||
* @property {string} description - Optional description of the macro
|
||||
*/
|
||||
|
||||
export class MacrosParser {
|
||||
/**
|
||||
* A map of registered macros.
|
||||
@@ -33,12 +39,29 @@ export class MacrosParser {
|
||||
*/
|
||||
static #macros = new Map();
|
||||
|
||||
/**
|
||||
* A map of macro descriptions.
|
||||
* @type {Map<string, string>}
|
||||
*/
|
||||
static #descriptions = new Map();
|
||||
|
||||
/**
|
||||
* Returns an iterator over all registered macros.
|
||||
* @returns {IterableIterator<CustomMacro>}
|
||||
*/
|
||||
static [Symbol.iterator] = function* () {
|
||||
for (const macro of MacrosParser.#macros.keys()) {
|
||||
yield { key: macro, description: MacrosParser.#descriptions.get(macro) };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a global macro that can be used anywhere where substitution is allowed.
|
||||
* @param {string} key Macro name (key)
|
||||
* @param {string|MacroFunction} value A string or a function that returns a string
|
||||
* @param {string} [description] Optional description of the macro
|
||||
*/
|
||||
static registerMacro(key, value) {
|
||||
static registerMacro(key, value, description = '') {
|
||||
if (typeof key !== 'string') {
|
||||
throw new Error('Macro key must be a string');
|
||||
}
|
||||
@@ -64,6 +87,10 @@ export class MacrosParser {
|
||||
}
|
||||
|
||||
this.#macros.set(key, value);
|
||||
|
||||
if (typeof description === 'string' && description) {
|
||||
this.#descriptions.set(key, description);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,6 +115,8 @@ export class MacrosParser {
|
||||
if (!deleted) {
|
||||
console.warn(`Macro ${key} was not registered`);
|
||||
}
|
||||
|
||||
this.#descriptions.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,10 +231,19 @@ export function getLastMessageId({ exclude_swipe_in_propress = true, filter = nu
|
||||
* @returns {number|null} The ID of the first message in the context
|
||||
*/
|
||||
function getFirstIncludedMessageId() {
|
||||
const index = Number(document.querySelector('.lastInContext')?.getAttribute('mesid'));
|
||||
return chat_metadata['lastInContextMessageId'];
|
||||
}
|
||||
|
||||
if (!isNaN(index) && index >= 0) {
|
||||
return index;
|
||||
/**
|
||||
* Returns the ID of the first displayed message in the chat.
|
||||
*
|
||||
* @returns {number|null} The ID of the first displayed message
|
||||
*/
|
||||
function getFirstDisplayedMessageId() {
|
||||
const mesId = Number(document.querySelector('#chat .mes')?.getAttribute('mesid'));
|
||||
|
||||
if (!isNaN(mesId) && mesId >= 0) {
|
||||
return mesId;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -467,6 +505,7 @@ export function evaluateMacros(content, env, postProcessFn) {
|
||||
{ regex: /{{lastUserMessage}}/gi, replace: () => getLastUserMessage() },
|
||||
{ regex: /{{lastCharMessage}}/gi, replace: () => getLastCharMessage() },
|
||||
{ regex: /{{firstIncludedMessageId}}/gi, replace: () => String(getFirstIncludedMessageId() ?? '') },
|
||||
{ regex: /{{firstDisplayedMessageId}}/gi, replace: () => String(getFirstDisplayedMessageId() ?? '') },
|
||||
{ regex: /{{lastSwipeId}}/gi, replace: () => String(getLastSwipeId() ?? '') },
|
||||
{ regex: /{{currentSwipeId}}/gi, replace: () => String(getCurrentSwipeId() ?? '') },
|
||||
{ regex: /{{reverse:(.+?)}}/gi, replace: (_, str) => Array.from(str).reverse().join('') },
|
||||
|
@@ -1922,7 +1922,7 @@ 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].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].includes(oai_settings.chat_completion_source)) {
|
||||
await validateReverseProxy();
|
||||
generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
|
||||
generate_data['proxy_password'] = oai_settings.proxy_password;
|
||||
@@ -2030,6 +2030,16 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
// https://api-docs.deepseek.com/api/create-chat-completion
|
||||
if (isDeepSeek) {
|
||||
generate_data.top_p = generate_data.top_p || Number.EPSILON;
|
||||
|
||||
if (generate_data.model.endsWith('-reasoner')) {
|
||||
delete generate_data.top_p;
|
||||
delete generate_data.temperature;
|
||||
delete generate_data.frequency_penalty;
|
||||
delete generate_data.presence_penalty;
|
||||
delete generate_data.top_logprobs;
|
||||
delete generate_data.logprobs;
|
||||
delete generate_data.logit_bias;
|
||||
}
|
||||
}
|
||||
|
||||
if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
|
||||
@@ -2085,6 +2095,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
let text = '';
|
||||
const swipes = [];
|
||||
const toolCalls = [];
|
||||
const state = {};
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) return;
|
||||
@@ -2095,9 +2106,9 @@ 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);
|
||||
swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state);
|
||||
} else {
|
||||
text += getStreamingReply(parsed);
|
||||
text += getStreamingReply(parsed, state);
|
||||
}
|
||||
|
||||
ToolManager.parseToolCalls(toolCalls, parsed);
|
||||
@@ -2129,14 +2140,27 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
}
|
||||
}
|
||||
|
||||
function getStreamingReply(data) {
|
||||
/**
|
||||
* Extracts the reply from the response data from a chat completions-like source
|
||||
* @param {object} data Response data from the chat completions-like source
|
||||
* @param {object} state Additional state to keep track of
|
||||
* @returns {string} The reply extracted from the response data
|
||||
*/
|
||||
function getStreamingReply(data, state) {
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
|
||||
return data?.delta?.text || '';
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
|
||||
return data?.candidates?.[0]?.content?.parts?.filter(x => oai_settings.show_thoughts || !x.thought)?.map(x => x.text)?.filter(x => x)?.join('\n\n') || '';
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
|
||||
return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
|
||||
} else {
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
|
||||
const hadThoughts = state.hadThoughts;
|
||||
const thoughts = data.choices?.filter(x => oai_settings.show_thoughts || !x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '';
|
||||
const content = data.choices?.[0]?.delta?.content || '';
|
||||
state.hadThoughts = !!thoughts;
|
||||
const separator = hadThoughts && !thoughts ? '\n\n' : '';
|
||||
return [thoughts, separator, content].filter(x => x).join('\n\n');
|
||||
} else {
|
||||
return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
|
||||
}
|
||||
}
|
||||
@@ -3346,7 +3370,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].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].includes(oai_settings.chat_completion_source)) {
|
||||
await validateReverseProxy();
|
||||
}
|
||||
|
||||
@@ -3971,6 +3995,8 @@ function onSettingsPresetChange() {
|
||||
settings: oai_settings,
|
||||
savePreset: saveOpenAIPreset,
|
||||
}).finally(r => {
|
||||
$('.model_custom_select').empty();
|
||||
|
||||
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
|
||||
if (preset[key] !== undefined) {
|
||||
if (isCheckbox) {
|
||||
@@ -4202,7 +4228,7 @@ async function onModelChange() {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206')) {
|
||||
$('#openai_max_context').attr('max', max_2mil);
|
||||
} else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash-exp')) {
|
||||
} else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash-exp') || value.includes('gemini-2.0-flash-thinking-exp')) {
|
||||
$('#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);
|
||||
@@ -4486,7 +4512,7 @@ async function onModelChange() {
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
} else if (oai_settings.deepseek_model == 'deepseek-chat') {
|
||||
} else if (['deepseek-reasoner', 'deepseek-chat'].includes(oai_settings.deepseek_model)) {
|
||||
$('#openai_max_context').attr('max', max_64k);
|
||||
} else if (oai_settings.deepseek_model == 'deepseek-coder') {
|
||||
$('#openai_max_context').attr('max', max_16k);
|
||||
@@ -4723,7 +4749,7 @@ async function onConnectButtonClick(e) {
|
||||
await writeSecret(SECRET_KEYS.DEEPSEEK, api_key_deepseek);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.DEEPSEEK]) {
|
||||
if (!secret_state[SECRET_KEYS.DEEPSEEK] && !oai_settings.reverse_proxy) {
|
||||
console.log('No secret key saved for DeepSeek');
|
||||
return;
|
||||
}
|
||||
@@ -4899,6 +4925,8 @@ export function isImageInliningSupported() {
|
||||
const visionSupportedModels = [
|
||||
'gpt-4-vision',
|
||||
'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-1.5-flash',
|
||||
'gemini-1.5-flash-latest',
|
||||
@@ -5489,8 +5517,8 @@ export function initOpenAI() {
|
||||
|
||||
if (!isMobile()) {
|
||||
$('#model_openrouter_select').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
placeholder: t`Select a model`,
|
||||
searchInputPlaceholder: t`Search models...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
templateResult: getOpenRouterModelTemplate,
|
||||
|
@@ -2534,7 +2534,7 @@ async function loadUntilMesId(mesId) {
|
||||
let target;
|
||||
|
||||
while (getFirstDisplayedMessageId() > mesId && getFirstDisplayedMessageId() !== 0) {
|
||||
showMoreMessages();
|
||||
await showMoreMessages();
|
||||
await delay(1);
|
||||
target = $('#chat').find(`.mes[mesid=${mesId}]`);
|
||||
|
||||
@@ -3962,6 +3962,95 @@ $(document).ready(() => {
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'css-var',
|
||||
/** @param {{to: string, varname: string }} args @param {string} value @returns {string} */
|
||||
callback: (args, value) => {
|
||||
// Map enum to target selector
|
||||
const targetSelector = {
|
||||
chat: '#chat',
|
||||
background: '#bg1',
|
||||
gallery: '#gallery',
|
||||
zoomedAvatar: 'div.zoomed_avatar',
|
||||
}[args.to || 'chat'];
|
||||
|
||||
if (!targetSelector) {
|
||||
toastr.error(`Invalid target: ${args.to}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.varname) {
|
||||
toastr.error('CSS variable name is required');
|
||||
return;
|
||||
}
|
||||
if (!args.varname.startsWith('--')) {
|
||||
toastr.error('CSS variable names must start with "--"');
|
||||
return;
|
||||
}
|
||||
|
||||
const elements = document.querySelectorAll(targetSelector);
|
||||
if (elements.length === 0) {
|
||||
toastr.error(`No elements found for ${args.to ?? 'chat'} with selector "${targetSelector}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
elements.forEach(element => {
|
||||
element.style.setProperty(args.varname, value);
|
||||
});
|
||||
|
||||
console.info(`Set CSS variable "${args.varname}" to "${value}" on "${targetSelector}"`);
|
||||
},
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'varname',
|
||||
description: 'CSS variable name (starting with double dashes)',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'to',
|
||||
description: 'The target element to which the CSS variable will be applied',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('chat', null, enumTypes.enum, enumIcons.message),
|
||||
new SlashCommandEnumValue('background', null, enumTypes.enum, enumIcons.image),
|
||||
new SlashCommandEnumValue('zoomedAvatar', null, enumTypes.enum, enumIcons.character),
|
||||
new SlashCommandEnumValue('gallery', null, enumTypes.enum, enumIcons.image),
|
||||
],
|
||||
defaultValue: 'chat',
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'CSS variable value',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Sets a CSS variable to a specified value on a target element.
|
||||
<br />
|
||||
Only setting of variable names is supported. They have to be prefixed with double dashes ("--exampleVar").
|
||||
Setting actual CSS properties is not supported. Custom CSS in the theme settings can be used for that.
|
||||
<br /><br />
|
||||
<b>This value will be gone after a page reload!</b>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code>/css-var varname="--SmartThemeBodyColor" #ff0000</code></pre>
|
||||
Sets the text color of the chat to red
|
||||
</li>
|
||||
<li>
|
||||
<pre><code>/css-var to=zoomedAvatar varname="--SmartThemeBlurStrength" 0</code></pre>
|
||||
Remove the blur from the zoomed avatar
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'movingui',
|
||||
callback: setmovingUIPreset,
|
||||
|
@@ -39,6 +39,7 @@ import {
|
||||
setCharacterName,
|
||||
setExtensionPrompt,
|
||||
setUserName,
|
||||
showMoreMessages,
|
||||
stopGeneration,
|
||||
substituteParams,
|
||||
system_avatar,
|
||||
@@ -1964,6 +1965,39 @@ export function initDefaultSlashCommands() {
|
||||
returns: ARGUMENT_TYPE.BOOLEAN,
|
||||
helpString: 'Returns true if the current device is a mobile device, false otherwise. Equivalent to <code>{{isMobile}}</code> macro.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'chat-render',
|
||||
helpString: 'Renders a specified number of messages into the chat window. Displays all messages if no argument is provided.',
|
||||
callback: async (args, number) => {
|
||||
await showMoreMessages(number && !isNaN(Number(number)) ? Number(number) : Number.MAX_SAFE_INTEGER);
|
||||
if (isTrueBoolean(String(args?.scroll ?? ''))) {
|
||||
$('#chat').scrollTop(0);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'scroll',
|
||||
description: 'scroll to the top after rendering',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'false',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'number of messages', [ARGUMENT_TYPE.NUMBER], false,
|
||||
),
|
||||
],
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'chat-reload',
|
||||
helpString: 'Reloads the current chat.',
|
||||
callback: async () => {
|
||||
await reloadCurrentChat();
|
||||
return '';
|
||||
},
|
||||
}));
|
||||
|
||||
registerVariableCommands();
|
||||
}
|
||||
|
@@ -37,6 +37,7 @@ export const enumIcons = {
|
||||
voice: '🎤',
|
||||
server: '🖥️',
|
||||
popup: '🗔',
|
||||
image: '🖼️',
|
||||
|
||||
true: '✔️',
|
||||
false: '❌',
|
||||
|
@@ -22,6 +22,8 @@ import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
|
||||
import { SlashCommandDebugController } from './SlashCommandDebugController.js';
|
||||
import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandBreak } from './SlashCommandBreak.js';
|
||||
import { MacrosParser } from '../macros.js';
|
||||
import { t } from '../i18n.js';
|
||||
|
||||
/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */
|
||||
/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */
|
||||
@@ -494,6 +496,10 @@ export class SlashCommandParser {
|
||||
li.querySelector('tt').textContent,
|
||||
(li.querySelector('tt').remove(),li.innerHTML),
|
||||
));
|
||||
for (const macro of MacrosParser) {
|
||||
if (options.find(it => it.name === macro.key)) continue;
|
||||
options.push(new MacroAutoCompleteOption(macro.key, `{{${macro.key}}}`, macro.description || t`No description provided`));
|
||||
}
|
||||
const result = new AutoCompleteNameResult(
|
||||
macro.name,
|
||||
macro.start + 2,
|
||||
|
@@ -220,6 +220,21 @@ async function* parseStreamData(json) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (typeof json.choices[0].delta.reasoning_content === 'string' && json.choices[0].delta.reasoning_content.length > 0) {
|
||||
for (let j = 0; j < json.choices[0].delta.reasoning_content.length; j++) {
|
||||
const str = json.choices[0].delta.reasoning_content[j];
|
||||
const isLastSymbol = j === json.choices[0].delta.reasoning_content.length - 1;
|
||||
const choiceClone = structuredClone(json.choices[0]);
|
||||
choiceClone.delta.reasoning_content = str;
|
||||
choiceClone.delta.content = isLastSymbol ? choiceClone.delta.content : '';
|
||||
const choices = [choiceClone];
|
||||
yield {
|
||||
data: { ...json, choices },
|
||||
chunk: str,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {
|
||||
for (let j = 0; j < json.choices[0].delta.content.length; j++) {
|
||||
const str = json.choices[0].delta.content[j];
|
||||
|
@@ -12,6 +12,7 @@ import {
|
||||
extension_prompts,
|
||||
Generate,
|
||||
generateQuietPrompt,
|
||||
getCharacters,
|
||||
getCurrentChatId,
|
||||
getRequestHeaders,
|
||||
getThumbnailUrl,
|
||||
@@ -55,7 +56,7 @@ import { MacrosParser } from './macros.js';
|
||||
import { oai_settings } from './openai.js';
|
||||
import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
|
||||
import { power_user, registerDebugFunction } from './power-user.js';
|
||||
import { isMobile, shouldSendOnEnter } from './RossAscends-mods.js';
|
||||
import { humanizedDateTime, isMobile, shouldSendOnEnter } from './RossAscends-mods.js';
|
||||
import { ScraperManager } from './scrapers.js';
|
||||
import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
@@ -65,7 +66,7 @@ import { tag_map, tags } from './tags.js';
|
||||
import { textgenerationwebui_settings } from './textgen-settings.js';
|
||||
import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
|
||||
import { ToolManager } from './tool-calling.js';
|
||||
import { timestampToMoment } from './utils.js';
|
||||
import { timestampToMoment, uuidv4 } from './utils.js';
|
||||
|
||||
export function getContext() {
|
||||
return {
|
||||
@@ -167,6 +168,9 @@ export function getContext() {
|
||||
chatCompletionSettings: oai_settings,
|
||||
textCompletionSettings: textgenerationwebui_settings,
|
||||
powerUserSettings: power_user,
|
||||
getCharacters,
|
||||
uuidv4,
|
||||
humanizedDateTime,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -196,9 +196,10 @@ function filterByTagState(entities, { globalDisplayFilters = false, subForEntity
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hide folders that have 0 visible sub entities after the first filtering round
|
||||
// Hide folders that have 0 visible sub entities after the first filtering round, unless we are inside a search via search term.
|
||||
// Then we want to display folders that mach too, even if the chars inside don't match the search.
|
||||
if (entity.type === 'tag') {
|
||||
return entity.entities.length > 0;
|
||||
return entity.entities.length > 0 || entitiesFilter.getFilterData(FILTER_TYPES.SEARCH);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -1,3 +1,9 @@
|
||||
<div>
|
||||
<b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span>
|
||||
<div data-type="assistant_note">
|
||||
<div>
|
||||
<b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span>
|
||||
<span>Click the button to save it as a file.</span>
|
||||
</div>
|
||||
<div class="assistant_note_export menu_button menu_button_icon" title="Export as JSONL">
|
||||
<i class="fa-solid fa-file-export"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<li><span data-i18n="char_import_2">Chub Lorebook (Direct Link or ID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>lorebooks/bartleby/example-lorebook</tt></li>
|
||||
<li><span data-i18n="char_import_3">JanitorAI Character (Direct Link or UUID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>ddd1498a-a370-4136-b138-a8cd9461fdfe_character-aqua-the-useless-goddess</tt></li>
|
||||
<li><span data-i18n="char_import_4">Pygmalion.chat Character (Direct Link or UUID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>a7ca95a1-0c88-4e23-91b3-149db1e78ab9</tt></li>
|
||||
<li><span data-i18n="char_import_5">AICharacterCard.com Character (Direct Link or ID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>AICC/aicharcards/the-game-master</tt></li>
|
||||
<li><span data-i18n="char_import_5">AICharacterCards.com Character (Direct Link or ID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>AICC/aicharcards/the-game-master</tt></li>
|
||||
<li><span data-i18n="char_import_6">Direct PNG Link (refer to</span> <code>config.yaml</code><span data-i18n="char_import_7"> for allowed hosts)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://files.catbox.moe/notarealfile.png</tt></li>
|
||||
<li><span data-i18n="char_import_8">RisuRealm Character (Direct Link)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://realm.risuai.net/character/3ca54c71-6efe-46a2-b9d0-4f62df23d712</tt></li>
|
||||
</ul>
|
||||
|
@@ -139,7 +139,7 @@
|
||||
<div class="">{{finalPromptTokens}}</div>
|
||||
</div>
|
||||
<div class="flex-container wide100p">
|
||||
<div class="flex1"><span data-i18n="Max Context">Max Context</span> <small data-i18n="(Context Size - Response Length)">(Context Size - Response Length)</small><span data-i18n=":">:</span></div>
|
||||
<div class="flex1"><span data-i18n="Max Context">Max Context</span> <small data-i18n="(Context Size - Response Length)">(Context Size - Response Length)</small>:</div>
|
||||
<div class="">{{thisPrompt_max_context}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -28,7 +28,8 @@
|
||||
<li><tt>{{lastUserMessage}}</tt> – <span data-i18n="help_macros_lastUser">the text of the latest user chat message.</span></li>
|
||||
<li><tt>{{lastCharMessage}}</tt> – <span data-i18n="help_macros_lastChar">the text of the latest character chat message.</span></li>
|
||||
<li><tt>{{lastMessageId}}</tt> – <span data-i18n="help_macros_21">index # of the latest chat message. Useful for slash command batching.</span></li>
|
||||
<li><tt>{{firstIncludedMessageId}}</tt> – <span data-i18n="help_macros_22">the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</span></li>
|
||||
<li><tt>{{firstIncludedMessageId}}</tt> – <span data-i18n="help_macros_22">the ID of the first message included in the context. Requires generation to be run at least once in the current session. Will only be updated on generation.</span></li>
|
||||
<li><tt>{{firstDisplayedMessageId}}</tt> – <span data-i18n="help_macros_firstDisplayedMessageId">the ID of the first message loaded into the visible chat.</span></li>
|
||||
<li><tt>{{currentSwipeId}}</tt> – <span data-i18n="help_macros_23">the 1-based ID of the current swipe in the last chat message. Empty string if the last message is user or prompt-hidden.</span></li>
|
||||
<li><tt>{{lastSwipeId}}</tt> – <span data-i18n="help_macros_24">the number of swipes in the last chat message. Empty string if the last message is user or prompt-hidden.</span></li>
|
||||
<li><tt>{{reverse:(content)}}</tt> – <span data-i18n="help_macros_reverse">reverses the content of the macro.</span></li>
|
||||
|
@@ -5,6 +5,7 @@ import { textgenerationwebui_settings as textgen_settings, textgen_types } from
|
||||
import { tokenizers } from './tokenizers.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||
import { t } from './i18n.js';
|
||||
|
||||
let mancerModels = [];
|
||||
let togetherModels = [];
|
||||
@@ -318,8 +319,6 @@ export async function loadFeatherlessModels(data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort the data by model id (default A-Z)
|
||||
data.sort((a, b) => a.id.localeCompare(b.id));
|
||||
originalModels = data; // Store the original data for search
|
||||
featherlessModels = data;
|
||||
|
||||
@@ -333,10 +332,8 @@ export async function loadFeatherlessModels(data) {
|
||||
// Retrieve the stored number of items per page or default to 10
|
||||
const perPage = Number(localStorage.getItem(storageKey)) || 10;
|
||||
|
||||
// Initialize pagination with the full set of models
|
||||
const currentModelIndex = data.findIndex(x => x.id === textgen_settings.featherless_model);
|
||||
featherlessCurrentPage = currentModelIndex >= 0 ? (currentModelIndex / perPage) + 1 : 1;
|
||||
setupPagination(originalModels, perPage);
|
||||
// Initialize pagination
|
||||
applyFiltersAndSort();
|
||||
|
||||
// Function to set up pagination (also used for filtered results)
|
||||
function setupPagination(models, perPage, pageNumber = featherlessCurrentPage) {
|
||||
@@ -382,7 +379,7 @@ export async function loadFeatherlessModels(data) {
|
||||
|
||||
const dateAddedDiv = document.createElement('div');
|
||||
dateAddedDiv.classList.add('model-date-added');
|
||||
dateAddedDiv.textContent = `Added On: ${new Date(model.updated_at).toLocaleDateString()}`;
|
||||
dateAddedDiv.textContent = `Added On: ${new Date(model.created * 1000).toLocaleDateString()}`;
|
||||
|
||||
detailsContainer.appendChild(modelClassDiv);
|
||||
detailsContainer.appendChild(contextLengthDiv);
|
||||
@@ -471,6 +468,7 @@ export async function loadFeatherlessModels(data) {
|
||||
featherlessTop = await fetchFeatherlessStats();
|
||||
}
|
||||
const featherlessIds = featherlessTop.map(stat => stat.id);
|
||||
|
||||
if (selectedCategory === 'New') {
|
||||
featherlessNew = await fetchFeatherlessNew();
|
||||
}
|
||||
@@ -492,7 +490,7 @@ export async function loadFeatherlessModels(data) {
|
||||
return matchesSearch && matchesClass && matchesNew;
|
||||
}
|
||||
else {
|
||||
return matchesSearch;
|
||||
return matchesSearch && matchesClass;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -501,11 +499,14 @@ export async function loadFeatherlessModels(data) {
|
||||
} else if (selectedSortOrder === 'desc') {
|
||||
filteredModels.sort((a, b) => b.id.localeCompare(a.id));
|
||||
} else if (selectedSortOrder === 'date_asc') {
|
||||
filteredModels.sort((a, b) => a.updated_at.localeCompare(b.updated_at));
|
||||
filteredModels.sort((a, b) => a.created - b.created);
|
||||
} else if (selectedSortOrder === 'date_desc') {
|
||||
filteredModels.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
||||
filteredModels.sort((a, b) => b.created - a.created);
|
||||
}
|
||||
|
||||
const currentModelIndex = filteredModels.findIndex(x => x.id === textgen_settings.featherless_model);
|
||||
featherlessCurrentPage = currentModelIndex >= 0 ? (currentModelIndex / perPage) + 1 : 1;
|
||||
|
||||
setupPagination(filteredModels, Number(localStorage.getItem(storageKey)) || perPage, featherlessCurrentPage);
|
||||
}
|
||||
|
||||
@@ -527,7 +528,7 @@ async function fetchFeatherlessStats() {
|
||||
}
|
||||
|
||||
async function fetchFeatherlessNew() {
|
||||
const response = await fetch('https://api.featherless.ai/feather/models?sort=-created_at&perPage=10');
|
||||
const response = await fetch('https://api.featherless.ai/feather/models?sort=-created_at&perPage=20');
|
||||
const data = await response.json();
|
||||
return data.items;
|
||||
}
|
||||
@@ -936,71 +937,71 @@ export function initTextGenModels() {
|
||||
|
||||
if (!isMobile()) {
|
||||
$('#mancer_model').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
placeholder: t`Select a model`,
|
||||
searchInputPlaceholder: t`Search models...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
templateResult: getMancerModelTemplate,
|
||||
});
|
||||
$('#model_togetherai_select').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
placeholder: t`Select a model`,
|
||||
searchInputPlaceholder: t`Search models...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
templateResult: getTogetherModelTemplate,
|
||||
});
|
||||
$('#ollama_model').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
placeholder: t`Select a model`,
|
||||
searchInputPlaceholder: t`Search models...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
});
|
||||
$('#tabby_model').select2({
|
||||
placeholder: '[Currently loaded]',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
placeholder: t`[Currently loaded]`,
|
||||
searchInputPlaceholder: t`Search models...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
allowClear: true,
|
||||
});
|
||||
$('#model_infermaticai_select').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
placeholder: t`Select a model`,
|
||||
searchInputPlaceholder: t`Search models...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
templateResult: getInfermaticAIModelTemplate,
|
||||
});
|
||||
$('#model_dreamgen_select').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
placeholder: t`Select a model`,
|
||||
searchInputPlaceholder: t`Search models...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
templateResult: getDreamGenModelTemplate,
|
||||
});
|
||||
$('#openrouter_model').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
placeholder: t`Select a model`,
|
||||
searchInputPlaceholder: t`Search models...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
templateResult: getOpenRouterModelTemplate,
|
||||
});
|
||||
$('#vllm_model').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
placeholder: t`Select a model`,
|
||||
searchInputPlaceholder: t`Search models...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
templateResult: getVllmModelTemplate,
|
||||
});
|
||||
$('#aphrodite_model').select2({
|
||||
placeholder: 'Select a model',
|
||||
searchInputPlaceholder: 'Search models...',
|
||||
placeholder: t`Select a model`,
|
||||
searchInputPlaceholder: t`Search models...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
templateResult: getAphroditeModelTemplate,
|
||||
});
|
||||
providersSelect.select2({
|
||||
sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),
|
||||
placeholder: 'Select providers. No selection = all providers.',
|
||||
searchInputPlaceholder: 'Search providers...',
|
||||
placeholder: t`Select providers. No selection = all providers.`,
|
||||
searchInputPlaceholder: t`Search providers...`,
|
||||
searchInputCssClass: 'text_pole',
|
||||
width: '100%',
|
||||
closeOnSelect: false,
|
||||
|
@@ -1231,7 +1231,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
||||
'top_p': settings.top_p,
|
||||
'typical_p': settings.typical_p,
|
||||
'typical': settings.typical_p,
|
||||
'sampler_seed': settings.seed,
|
||||
'sampler_seed': settings.seed >= 0 ? settings.seed : undefined,
|
||||
'min_p': settings.min_p,
|
||||
'repetition_penalty': settings.rep_pen,
|
||||
'frequency_penalty': settings.freq_pen,
|
||||
@@ -1294,7 +1294,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
||||
'temperature_last': (settings.type === OOBA || settings.type === APHRODITE || settings.type == TABBY) ? settings.temperature_last : undefined,
|
||||
'speculative_ngram': settings.type === TABBY ? settings.speculative_ngram : undefined,
|
||||
'do_sample': settings.type === OOBA ? settings.do_sample : undefined,
|
||||
'seed': settings.seed,
|
||||
'seed': settings.seed >= 0 ? settings.seed : undefined,
|
||||
'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1,
|
||||
'negative_prompt': cfgValues?.negativePrompt ?? substituteParams(settings.negative_prompt) ?? '',
|
||||
'grammar_string': settings.grammar_string,
|
||||
|
@@ -67,6 +67,16 @@ export function escapeHtml(str) {
|
||||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make string safe for use as a CSS selector.
|
||||
* @param {string} str String to sanitize
|
||||
* @param {string} replacement Replacement for invalid characters
|
||||
* @returns {string} Sanitized string
|
||||
*/
|
||||
export function sanitizeSelector(str, replacement = '_') {
|
||||
return String(str).replace(/[^a-z0-9_-]/ig, replacement);
|
||||
}
|
||||
|
||||
export function isValidUrl(value) {
|
||||
try {
|
||||
new URL(value);
|
||||
@@ -381,6 +391,26 @@ export function getStringHash(str, seed = 0) {
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy text to clipboard. Use navigator.clipboard.writeText if available, otherwise use document.execCommand.
|
||||
* @param {string} text - The text to copy to the clipboard.
|
||||
* @returns {Promise<void>} A promise that resolves when the text has been copied to the clipboard.
|
||||
*/
|
||||
export function copyText(text) {
|
||||
if (navigator.clipboard) {
|
||||
return navigator.clipboard.writeText(text);
|
||||
}
|
||||
|
||||
const parent = document.querySelector('dialog[open]:last-of-type') ?? document.body;
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
parent.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
parent.removeChild(textArea);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of debounced functions to their timers.
|
||||
* Weak map is used to avoid memory leaks.
|
||||
|
@@ -20,6 +20,7 @@ import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||
import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
|
||||
import { StructuredCloneMap } from './util/StructuredCloneMap.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { t } from './i18n.js';
|
||||
|
||||
export const world_info_insertion_strategy = {
|
||||
evenly: 0,
|
||||
@@ -909,21 +910,21 @@ function registerWorldInfoSlashCommands() {
|
||||
|
||||
async function getEntriesFromFile(file) {
|
||||
if (!file || !world_names.includes(file)) {
|
||||
toastr.warning('Valid World Info file name is required');
|
||||
toastr.warning(t`Valid World Info file name is required`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const data = await loadWorldInfo(file);
|
||||
|
||||
if (!data || !('entries' in data)) {
|
||||
toastr.warning('World Info file has an invalid format');
|
||||
toastr.warning(t`World Info file has an invalid format`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const entries = Object.values(data.entries);
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
toastr.warning('World Info file has no entries');
|
||||
toastr.warning(t`World Info file has no entries`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -951,7 +952,7 @@ function registerWorldInfoSlashCommands() {
|
||||
name = String(name ?? '') || context.characters[context.characterId]?.avatar || null;
|
||||
const character = findChar({ name });
|
||||
if (!character) {
|
||||
toastr.error('Character not found.');
|
||||
toastr.error(t`Character not found.`);
|
||||
return '';
|
||||
}
|
||||
const books = [];
|
||||
@@ -977,7 +978,7 @@ function registerWorldInfoSlashCommands() {
|
||||
const chatId = getCurrentChatId();
|
||||
|
||||
if (!chatId) {
|
||||
toastr.warning('Open a chat to get a name of the chat-bound lorebook');
|
||||
toastr.warning(t`Open a chat to get a name of the chat-bound lorebook`);
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -4773,7 +4774,7 @@ export async function importEmbeddedWorldInfo(skipPopup = false) {
|
||||
const bookName = characters[chid]?.data?.character_book?.name || `${characters[chid]?.name}'s Lorebook`;
|
||||
|
||||
if (!skipPopup) {
|
||||
const confirmation = await Popup.show.confirm(`Are you sure you want to import "${bookName}"?`, world_names.includes(bookName) ? 'It will overwrite the World/Lorebook with the same name.' : '');
|
||||
const confirmation = await Popup.show.confirm(t`Are you sure you want to import '${bookName}'?`, world_names.includes(bookName) ? t`It will overwrite the World/Lorebook with the same name.` : '');
|
||||
if (!confirmation) {
|
||||
return;
|
||||
}
|
||||
@@ -4785,7 +4786,7 @@ export async function importEmbeddedWorldInfo(skipPopup = false) {
|
||||
await updateWorldInfoList();
|
||||
$('#character_world').val(bookName).trigger('change');
|
||||
|
||||
toastr.success(`The world "${bookName}" has been imported and linked to the character successfully.`, 'World/Lorebook imported');
|
||||
toastr.success(t`The world '${bookName}' has been imported and linked to the character successfully.`, t`World/Lorebook imported`);
|
||||
|
||||
const newIndex = world_names.indexOf(bookName);
|
||||
if (newIndex >= 0) {
|
||||
@@ -4813,9 +4814,9 @@ export function onWorldInfoChange(args, text) {
|
||||
if (selected_world_info.includes(name)) {
|
||||
selected_world_info.splice(selected_world_info.indexOf(name), 1);
|
||||
wiElement.prop('selected', false);
|
||||
if (!silent) toastr.success(`Deactivated world: ${name}`);
|
||||
if (!silent) toastr.success(t`Deactivated world: ${name}`);
|
||||
} else {
|
||||
if (!silent) toastr.error(`World was not active: ${name}`);
|
||||
if (!silent) toastr.error(t`World was not active: ${name}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -4823,11 +4824,11 @@ export function onWorldInfoChange(args, text) {
|
||||
if (selected_world_info.includes(name)) {
|
||||
selected_world_info.splice(selected_world_info.indexOf(name), 1);
|
||||
wiElement.prop('selected', false);
|
||||
if (!silent) toastr.success(`Deactivated world: ${name}`);
|
||||
if (!silent) toastr.success(t`Deactivated world: ${name}`);
|
||||
} else {
|
||||
selected_world_info.push(name);
|
||||
wiElement.prop('selected', true);
|
||||
if (!silent) toastr.success(`Activated world: ${name}`);
|
||||
if (!silent) toastr.success(t`Activated world: ${name}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -4835,16 +4836,16 @@ export function onWorldInfoChange(args, text) {
|
||||
default: {
|
||||
selected_world_info.push(name);
|
||||
wiElement.prop('selected', true);
|
||||
if (!silent) toastr.success(`Activated world: ${name}`);
|
||||
if (!silent) toastr.success(t`Activated world: ${name}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!silent) toastr.error(`No world found named: ${worldName}`);
|
||||
if (!silent) toastr.error(t`No world found named: ${worldName}`);
|
||||
}
|
||||
});
|
||||
$('#world_info').trigger('change');
|
||||
} else { // if no args, unset all worlds
|
||||
if (!silent) toastr.success('Deactivated all worlds');
|
||||
if (!silent) toastr.success(t`Deactivated all worlds`);
|
||||
selected_world_info = [];
|
||||
$('#world_info').val(null).trigger('change');
|
||||
}
|
||||
@@ -4860,7 +4861,7 @@ export function onWorldInfoChange(args, text) {
|
||||
} else {
|
||||
const wiElement = getWIElement(existingWorldName);
|
||||
wiElement.prop('selected', false);
|
||||
toastr.error(`The world with ${existingWorldName} is invalid or corrupted.`);
|
||||
toastr.error(t`The world with ${existingWorldName} is invalid or corrupted.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4892,7 +4893,7 @@ export async function importWorldInfo(file) {
|
||||
}
|
||||
|
||||
if (jsonData === undefined || jsonData === null) {
|
||||
toastr.error(`File is not valid: ${file.name}`);
|
||||
toastr.error(t`File is not valid: ${file.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5038,7 +5039,7 @@ jQuery(() => {
|
||||
|
||||
$('#world_create_button').on('click', async () => {
|
||||
const tempName = getFreeWorldName();
|
||||
const finalName = await Popup.show.input('Create a new World Info', 'Enter a name for the new file:', tempName);
|
||||
const finalName = await Popup.show.input(t`Create a new World Info`, t`Enter a name for the new file:`, tempName);
|
||||
|
||||
if (finalName) {
|
||||
await createNewWorldInfo(finalName, { interactive: true });
|
||||
|
Reference in New Issue
Block a user