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:
		@@ -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),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user