Merge branch 'staging' into parser-post-stuff

This commit is contained in:
LenAnderson
2024-05-18 14:51:06 -04:00
37 changed files with 1704 additions and 160 deletions

View File

@@ -4,7 +4,7 @@
Enter a URL or the ID of a Fandom wiki page to scrape:
</label>
<small>
<span data-i18n=Examples:">Examples:</span>
<span data-i18n="Examples:">Examples:</span>
<code>https://harrypotter.fandom.com/</code>
<span data-i18n="or">or</span>
<code>harrypotter</code>

View File

@@ -7,7 +7,7 @@
Don't include the page name!
</i>
<small>
<span data-i18n=Examples:">Examples:</span>
<span data-i18n="Examples:">Examples:</span>
<code>https://streetcat.wiki/index.php</code>
<span data-i18n="or">or</span>
<code>https://tcrf.net</code>

View File

@@ -1020,12 +1020,12 @@ function parseLlmResponse(emotionResponse, labels) {
const parsedEmotion = JSON.parse(emotionResponse);
return parsedEmotion?.emotion ?? fallbackExpression;
} catch {
const fuse = new Fuse([emotionResponse]);
for (const label of labels) {
const result = fuse.search(label);
if (result.length > 0) {
return label;
}
const fuse = new Fuse(labels, { includeScore: true });
console.debug('Using fuzzy search in labels:', labels);
const result = fuse.search(emotionResponse);
if (result.length > 0) {
console.debug(`fuzzy search found: ${result[0].item} as closest for the LLM response:`, emotionResponse);
return result[0].item;
}
}

View File

@@ -342,6 +342,16 @@ export class QuickReply {
message.addEventListener('scroll', (evt)=>{
updateScrollDebounced();
});
/** @type {any} */
const resizeListener = debounce((evt) => {
updateSyntax();
updateScrollDebounced(evt);
if (document.activeElement == message) {
message.blur();
message.focus();
}
});
window.addEventListener('resize', resizeListener);
message.style.color = 'transparent';
message.style.background = 'transparent';
message.style.setProperty('text-shadow', 'none', 'important');
@@ -514,6 +524,8 @@ export class QuickReply {
});
await popupResult;
window.removeEventListener('resize', resizeListener);
} else {
warn('failed to fetch qrEditor template');
}

View File

@@ -266,6 +266,7 @@ const default_settings = {
show_external_models: false,
proxy_password: '',
assistant_prefill: '',
assistant_impersonation: '',
human_sysprompt_message: default_claude_human_sysprompt_message,
use_ai21_tokenizer: false,
use_google_tokenizer: false,
@@ -342,6 +343,7 @@ const oai_settings = {
show_external_models: false,
proxy_password: '',
assistant_prefill: '',
assistant_impersonation: '',
human_sysprompt_message: default_claude_human_sysprompt_message,
use_ai21_tokenizer: false,
use_google_tokenizer: false,
@@ -1767,7 +1769,7 @@ async function sendOpenAIRequest(type, messages, signal) {
generate_data['human_sysprompt_message'] = substituteParams(oai_settings.human_sysprompt_message);
// Don't add a prefill on quiet gens (summarization)
if (!isQuiet) {
generate_data['assistant_prefill'] = substituteParams(oai_settings.assistant_prefill);
generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);
}
}
@@ -2760,6 +2762,7 @@ function loadOpenAISettings(data, settings) {
oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;
oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password;
oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;
oai_settings.assistant_impersonation = settings.assistant_impersonation ?? default_settings.assistant_impersonation;
oai_settings.human_sysprompt_message = settings.human_sysprompt_message ?? default_settings.human_sysprompt_message;
oai_settings.image_inlining = settings.image_inlining ?? default_settings.image_inlining;
oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality;
@@ -2796,6 +2799,7 @@ function loadOpenAISettings(data, settings) {
$('#api_url_scale').val(oai_settings.api_url_scale);
$('#openai_proxy_password').val(oai_settings.proxy_password);
$('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
$('#claude_assistant_impersonation').val(oai_settings.assistant_impersonation);
$('#claude_human_sysprompt_textarea').val(oai_settings.human_sysprompt_message);
$('#openai_image_inlining').prop('checked', oai_settings.image_inlining);
$('#openai_bypass_status_check').prop('checked', oai_settings.bypass_status_check);
@@ -3115,6 +3119,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
api_url_scale: settings.api_url_scale,
show_external_models: settings.show_external_models,
assistant_prefill: settings.assistant_prefill,
assistant_impersonation: settings.assistant_impersonation,
human_sysprompt_message: settings.human_sysprompt_message,
use_ai21_tokenizer: settings.use_ai21_tokenizer,
use_google_tokenizer: settings.use_google_tokenizer,
@@ -3501,6 +3506,7 @@ function onSettingsPresetChange() {
show_external_models: ['#openai_show_external_models', 'show_external_models', true],
proxy_password: ['#openai_proxy_password', 'proxy_password', false],
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false],
human_sysprompt_message: ['#claude_human_sysprompt_textarea', 'human_sysprompt_message', false],
use_ai21_tokenizer: ['#use_ai21_tokenizer', 'use_ai21_tokenizer', true],
use_google_tokenizer: ['#use_google_tokenizer', 'use_google_tokenizer', true],
@@ -3526,6 +3532,11 @@ function onSettingsPresetChange() {
preset.names_behavior = character_names_behavior.COMPLETION;
}
// Claude: Assistant Impersonation Prefill = Inherit from Assistant Prefill
if (preset.assistant_prefill !== undefined && preset.assistant_impersonation === undefined) {
preset.assistant_impersonation = preset.assistant_prefill;
}
const updateInput = (selector, value) => $(selector).val(value).trigger('input');
const updateCheckbox = (selector, value) => $(selector).prop('checked', value).trigger('input');
@@ -4721,6 +4732,11 @@ $(document).ready(async function () {
saveSettingsDebounced();
});
$('#claude_assistant_impersonation').on('input', function () {
oai_settings.assistant_impersonation = String($(this).val());
saveSettingsDebounced();
});
$('#claude_human_sysprompt_textarea').on('input', function () {
oai_settings.human_sysprompt_message = String($('#claude_human_sysprompt_textarea').val());
saveSettingsDebounced();

View File

@@ -114,7 +114,9 @@ class PresetManager {
* @returns {any} Preset value
*/
findPreset(name) {
return $(this.select).find(`option:contains(${name})`).val();
return $(this.select).find('option').filter(function() {
return $(this).text() === name;
}).val();
}
/**

View File

@@ -447,8 +447,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unhide',
],
helpString: 'Unhides a message from the prompt.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'disable',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-disable',
callback: disableGroupMemberCallback,
aliases: ['disable', 'disablemember', 'memberdisable'],
unnamedArgumentList: [
new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@@ -456,7 +457,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'disable',
],
helpString: 'Disables a group member from being drafted for replies.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'enable',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-enable',
aliases: ['enable', 'enablemember', 'memberenable'],
callback: enableGroupMemberCallback,
unnamedArgumentList: [
new SlashCommandArgument(
@@ -465,9 +467,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'enable',
],
helpString: 'Enables a group member to be drafted for replies.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberadd',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-add',
callback: addGroupMemberCallback,
aliases: ['addmember'],
aliases: ['addmember', 'memberadd'],
unnamedArgumentList: [
new SlashCommandArgument(
'character name', [ARGUMENT_TYPE.STRING], true,
@@ -481,15 +483,15 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberadd',
<strong>Example:</strong>
<ul>
<li>
<pre><code>/memberadd John Doe</code></pre>
<pre><code>/member-add John Doe</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberremove',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-remove',
callback: removeGroupMemberCallback,
aliases: ['removemember'],
aliases: ['removemember', 'memberremove'],
unnamedArgumentList: [
new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@@ -503,16 +505,16 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberremove
<strong>Example:</strong>
<ul>
<li>
<pre><code>/memberremove 2</code></pre>
<pre><code>/memberremove John Doe</code></pre>
<pre><code>/member-remove 2</code></pre>
<pre><code>/member-remove John Doe</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberup',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-up',
callback: moveGroupMemberUpCallback,
aliases: ['upmember'],
aliases: ['upmember', 'memberup'],
unnamedArgumentList: [
new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@@ -520,9 +522,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberup',
],
helpString: 'Moves a group member up in the group chat list.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'memberdown',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'member-down',
callback: moveGroupMemberDownCallback,
aliases: ['downmember'],
aliases: ['downmember', 'memberdown'],
unnamedArgumentList: [
new SlashCommandArgument(
'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true,
@@ -2766,10 +2768,12 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
}
} catch (e) {
document.querySelector('#form_sheld').classList.add('script_error');
toastr.error(e.message);
result = new SlashCommandClosureResult();
result.isError = true;
result.errorMessage = e.message;
if (e.cause !== 'abort') {
toastr.error(e.message);
}
} finally {
delay(1000).then(()=>clearCommandProgressDebounced());

View File

@@ -58,6 +58,9 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
return new RegExp('=(.*)');
}
}
if (!Array.isArray(this.executor.command?.namedArgumentList)) {
return null;
}
const notProvidedNamedArguments = this.executor.command.namedArgumentList.filter(arg=>!this.executor.namedArgumentList.find(it=>it.name == arg.name));
let name;
let value;
@@ -130,6 +133,9 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
}
getUnnamedArgumentAt(text, index, isSelect) {
if (!Array.isArray(this.executor.command?.unnamedArgumentList)) {
return null;
}
const lastArgIsBlank = this.executor.unnamedArgumentList.slice(-1)[0]?.value == '';
const notProvidedArguments = this.executor.command.unnamedArgumentList.slice(this.executor.unnamedArgumentList.length - (lastArgIsBlank ? 1 : 0));
let value;

View File

@@ -14,10 +14,12 @@ import {
// eslint-disable-next-line no-unused-vars
import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
import { groupCandidatesFilter, groups, select_group_chats, selected_group } from './group-chats.js';
import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight } from './utils.js';
import { power_user } from './power-user.js';
import { debounce_timeout } from './constants.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
export {
TAG_FOLDER_TYPES,
@@ -357,7 +359,7 @@ function createTagMapFromList(listElement, key) {
* If you have an entity, you can get it's key easily via `getTagKeyForEntity(entity)`.
*
* @param {string} key - The key for which to get tags via the tag map
* @param {boolean} [sort=true] -
* @param {boolean} [sort=true] - Whether the tag list should be sorted
* @returns {Tag[]} A list of tags
*/
function getTagsList(key, sort = true) {
@@ -463,35 +465,122 @@ export function getTagKeyForEntityElement(element) {
return undefined;
}
/**
* Adds a tag to a given entity
* @param {Tag} tag - The tag to add
* @param {string|string[]} entityId - The entity to add this tag to. Has to be the entity key (e.g. `addTagToEntity`). (Also allows multiple entities to be passed in)
* @param {object} [options={}] - Optional arguments
* @param {JQuery<HTMLElement>|string?} [options.tagListSelector=null] - An optional selector if a specific list should be updated with the new tag too (for example because the add was triggered for that function)
* @param {PrintTagListOptions} [options.tagListOptions] - Optional parameters for printing the tag list. Can be set to be consistent with the expected behavior of tags in the list that was defined before.
* @returns {boolean} Whether at least one tag was added
*/
export function addTagToEntity(tag, entityId, { tagListSelector = null, tagListOptions = {} } = {}) {
let result = false;
// Add tags to the map
if (Array.isArray(entityId)) {
entityId.forEach((id) => result = addTagToMap(tag.id, id) || result);
} else {
result = addTagToMap(tag.id, entityId);
}
// Save and redraw
printCharactersDebounced();
saveSettingsDebounced();
// We should manually add the selected tag to the print tag function, so we cover places where the tag list did not automatically include it
tagListOptions.addTag = tag;
// add tag to the UI and internal map - we reprint so sorting and new markup is done correctly
if (tagListSelector) printTagList(tagListSelector, tagListOptions);
const inlineSelector = getInlineListSelector();
if (inlineSelector) {
printTagList($(inlineSelector), tagListOptions);
}
return result;
}
/**
* Removes a tag from a given entity
* @param {Tag} tag - The tag to remove
* @param {string|string[]} entityId - The entity to remove this tag from. Has to be the entity key (e.g. `addTagToEntity`). (Also allows multiple entities to be passed in)
* @param {object} [options={}] - Optional arguments
* @param {JQuery<HTMLElement>|string?} [options.tagListSelector=null] - An optional selector if a specific list should be updated with the tag removed too (for example because the add was triggered for that function)
* @param {JQuery<HTMLElement>?} [options.tagElement=null] - Optionally a direct html element of the tag to be removed, so it can be removed from the UI
* @returns {boolean} Whether at least one tag was removed
*/
export function removeTagFromEntity(tag, entityId, { tagListSelector = null, tagElement = null } = {}) {
let result = false;
// Remove tag from the map
if (Array.isArray(entityId)) {
entityId.forEach((id) => result = removeTagFromMap(tag.id, id) || result);
} else {
result = removeTagFromMap(tag.id, entityId);
}
// Save and redraw
printCharactersDebounced();
saveSettingsDebounced();
// We don't reprint the lists, we can just remove the html elements from them.
if (tagListSelector) {
const $selector = (typeof tagListSelector === 'string') ? $(tagListSelector) : tagListSelector;
$selector.find(`.tag[id="${tag.id}"]`).remove();
}
if (tagElement) tagElement.remove();
$(`${getInlineListSelector()} .tag[id="${tag.id}"]`).remove();
return result;
}
/**
* Adds a tag from a given character. If no character is provided, adds it from the currently active one.
* @param {string} tagId - The id of the tag
* @param {string} characterId - The id/key of the character or group
* @returns {boolean} Whether the tag was added or not
*/
function addTagToMap(tagId, characterId = null) {
const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey();
if (!key) {
return;
return false;
}
if (!Array.isArray(tag_map[key])) {
tag_map[key] = [tagId];
return true;
}
else {
if (tag_map[key].includes(tagId))
return false;
tag_map[key].push(tagId);
tag_map[key] = tag_map[key].filter(onlyUnique);
return true;
}
}
/**
* Removes a tag from a given character. If no character is provided, removes it from the currently active one.
* @param {string} tagId - The id of the tag
* @param {string} characterId - The id/key of the character or group
* @returns {boolean} Whether the tag was removed or not
*/
function removeTagFromMap(tagId, characterId = null) {
const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey();
if (!key) {
return;
return false;
}
if (!Array.isArray(tag_map[key])) {
tag_map[key] = [];
return false;
}
else {
const indexOf = tag_map[key].indexOf(tagId);
tag_map[key].splice(indexOf, 1);
return indexOf !== -1;
}
}
@@ -535,24 +624,7 @@ function selectTag(event, ui, listSelector, { tagListOptions = {} } = {}) {
const characterData = event.target.closest('#bulk_tags_div')?.dataset.characters;
const characterIds = characterData ? JSON.parse(characterData).characterIds : null;
if (characterIds) {
characterIds.forEach((characterId) => addTagToMap(tag.id, characterId));
} else {
addTagToMap(tag.id);
}
printCharactersDebounced();
saveSettingsDebounced();
// We should manually add the selected tag to the print tag function, so we cover places where the tag list did not automatically include it
tagListOptions.addTag = tag;
// add tag to the UI and internal map - we reprint so sorting and new markup is done correctly
printTagList(listSelector, tagListOptions);
const inlineSelector = getInlineListSelector();
if (inlineSelector) {
printTagList($(inlineSelector), tagListOptions);
}
addTagToEntity(tag, characterIds, { tagListSelector: listSelector, tagListOptions: tagListOptions });
// need to return false to keep the input clear
return false;
@@ -635,6 +707,7 @@ function createNewTag(tagName) {
create_date: Date.now(),
};
tags.push(tag);
console.debug('Created new tag', tag.name, 'with id', tag.id);
return tag;
}
@@ -932,8 +1005,8 @@ function updateTagFilterIndicator() {
function onTagRemoveClick(event) {
event.stopPropagation();
const tag = $(this).closest('.tag');
const tagId = tag.attr('id');
const tagElement = $(this).closest('.tag');
const tagId = tagElement.attr('id');
// Check if we are inside the drilldown. If so, we call remove on the bogus folder
if ($(this).closest('.rm_tag_bogus_drilldown').length > 0) {
@@ -942,24 +1015,13 @@ function onTagRemoveClick(event) {
return;
}
const tag = tags.find(t => t.id === tagId);
// Optional, check for multiple character ids being present.
const characterData = event.target.closest('#bulk_tags_div')?.dataset.characters;
const characterIds = characterData ? JSON.parse(characterData).characterIds : null;
tag.remove();
if (characterIds) {
characterIds.forEach((characterId) => removeTagFromMap(tagId, characterId));
} else {
removeTagFromMap(tagId);
}
$(`${getInlineListSelector()} .tag[id="${tagId}"]`).remove();
printCharactersDebounced();
saveSettingsDebounced();
removeTagFromEntity(tag, characterIds, { tagElement: tagElement });
}
// @ts-ignore
@@ -985,7 +1047,7 @@ function onGroupCreateClick() {
export function applyTagsOnCharacterSelect() {
//clearTagsFilter();
const chid = Number($(this).attr('chid'));
const chid = Number(this_chid);
printTagList($('#tagList'), { forEntityOrKey: chid, tagOptions: { removable: true } });
}
@@ -1461,14 +1523,200 @@ function printViewTagList(empty = true) {
}
}
function registerTagsSlashCommands() {
/**
* Gets the key for char/group for a slash command. If none can be found, a toastr will be shown and null returned.
* @param {string?} [charName] The optionally provided char name
* @returns {string?} - The char/group key, or null if none found
*/
function paraGetCharKey(charName) {
const entity = charName
? (characters.find(x => x.name === charName) || groups.find(x => x.name == charName))
: (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]);
const key = getTagKeyForEntity(entity);
if (!key) {
toastr.warning(`Character ${charName} not found.`);
return null;
}
return key;
}
/**
* Gets a tag by its name. Optionally can create the tag if it does not exist.
* @param {string} tagName - The name of the tag
* @param {object} options - Optional arguments
* @param {boolean} [options.allowCreate=false] - Whether a new tag should be created if no tag with the name exists
* @returns {Tag?} The tag, or null if not found
*/
function paraGetTag(tagName, { allowCreate = false } = {}) {
if (!tagName) {
toastr.warning('Tag name must be provided.');
return null;
}
let tag = tags.find(t => t.name === tagName);
if (allowCreate && !tag) {
tag = createNewTag(tagName);
}
if (!tag) {
toastr.warning(`Tag ${tagName} not found.`);
return null;
}
return tag;
}
function updateTagsList() {
switch (menu_type) {
case 'characters':
printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member);
break;
case 'character_edit':
applyTagsOnCharacterSelect();
break;
case 'group_edit':
select_group_chats(selected_group, true);
break;
}
}
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'tag-add',
returns: 'true/false - Whether the tag was added or was assigned already',
/** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */
callback: ({ name }, tagName) => {
const key = paraGetCharKey(name);
if (!key) return 'false';
const tag = paraGetTag(tagName, { allowCreate: true });
if (!tag) return 'false';
const result = addTagToEntity(tag, key);
updateTagsList();
return String(result);
},
namedArgumentList: [
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
],
unnamedArgumentList: [
new SlashCommandArgument('tag name', [ARGUMENT_TYPE.STRING], true),
],
helpString: `
<div>
Adds a tag to the character. If no character is provided, it adds it to the current character (<code>{{char}}</code>).
If the tag doesn't exist, it is created.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/tag-add name="Chloe" scenario</code></pre>
will add the tag "scenario" to the character named Chloe.
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'tag-remove',
returns: 'true/false - Whether the tag was removed or wasn\'t assigned already',
/** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */
callback: ({ name }, tagName) => {
const key = paraGetCharKey(name);
if (!key) return 'false';
const tag = paraGetTag(tagName);
if (!tag) return 'false';
const result = removeTagFromEntity(tag, key);
updateTagsList();
return String(result);
},
namedArgumentList: [
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
],
unnamedArgumentList: [
new SlashCommandArgument('tag name', [ARGUMENT_TYPE.STRING], true),
],
helpString: `
<div>
Removes a tag from the character. If no character is provided, it removes it from the current character (<code>{{char}}</code>).
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/tag-remove name="Chloe" scenario</code></pre>
will remove the tag "scenario" from the character named Chloe.
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'tag-exists',
returns: 'true/false - Whether the given tag name is assigned to the character',
/** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */
callback: ({ name }, tagName) => {
const key = paraGetCharKey(name);
if (!key) return 'false';
const tag = paraGetTag(tagName);
if (!tag) return 'false';
return String(tag_map[key].includes(tag.id));
},
namedArgumentList: [
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
],
unnamedArgumentList: [
new SlashCommandArgument('tag name', [ARGUMENT_TYPE.STRING], true),
],
helpString: `
<div>
Checks whether the given tag is assigned to the character. If no character is provided, it checks the current character (<code>{{char}}</code>).
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/tag-exists name="Chloe" scenario</code></pre>
will return true if the character named Chloe has the tag "scenario".
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'tag-list',
returns: 'Comma-separated list of all assigned tags',
/** @param {{name: string}} namedArgs @returns {string} */
callback: ({ name }) => {
const key = paraGetCharKey(name);
if (!key) return '';
const tags = getTagsList(key);
return tags.map(x => x.name).join(', ');
},
namedArgumentList: [
new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'),
],
helpString: `
<div>
Lists all assigned tags of the character. If no character is provided, it uses the current character (<code>{{char}}</code>).
<br />
Note that there is no special handling for tags containing commas, they will be printed as-is.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/tag-list name="Chloe"</code></pre>
could return something like <code>OC, scenario, edited, funny</code>
</li>
</ul>
</div>
`,
}));
}
export function initTags() {
createTagInput('#tagInput', '#tagList', { tagOptions: { removable: true } });
createTagInput('#groupTagInput', '#groupTagList', { tagOptions: { removable: true } });
$(document).on('click', '#rm_button_create', onCharacterCreateClick);
$(document).on('click', '#rm_button_group_chats', onGroupCreateClick);
$(document).on('click', '.character_select', applyTagsOnCharacterSelect);
$(document).on('click', '.group_select', applyTagsOnGroupSelect);
$(document).on('click', '.tag_remove', onTagRemoveClick);
$(document).on('input', '.tag_input', onTagInput);
$(document).on('click', '.tags_view', onViewTagsListClick);
@@ -1479,6 +1727,7 @@ export function initTags() {
$(document).on('click', '.tag_view_backup', onTagsBackupClick);
$(document).on('click', '.tag_view_restore', onBackupRestoreClick);
eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags);
eventSource.makeFirst(event_types.CHAT_CHANGED, () => selected_group ? applyTagsOnGroupSelect() : applyTagsOnCharacterSelect());
$(document).on('input', '#dialogue_popup input[name="auto_sort_tags"]', (evt) => {
const toggle = $(evt.target).is(':checked');
@@ -1506,4 +1755,6 @@ export function initTags() {
printCharactersDebounced();
}
}
registerTagsSlashCommands();
}

View File

@@ -11,9 +11,10 @@
<span data-i18n="Click ">Click </span><code><i class="fa-solid fa-plug"></i></code><span data-i18n="and select a"> and select a </span><a href="https://docs.sillytavern.app/usage/api-connections/" target="_blank" data-i18n="Chat API">Chat API</a>.</span>
</li>
<li>
<span data-i18n="Click ">Click </span><code><i class="fa-solid fa-address-card"></i></code><span data-i18n="and pick a character"> and pick a character</span>
<span data-i18n="Click ">Click </span><code><i class="fa-solid fa-address-card"></i></code><span data-i18n="and pick a character."> and pick a character.</span>
</li>
</ol>
<span data-i18n="You can browse a list of bundled characters in the Download Extensions & Assets menu within">You can browse a list of bundled characters in the <i>Download Extensions & Assets</i> menu within </span><code><i class="fa-solid fa-cubes"></i></code><span>.</span>
<hr>
<h3 data-i18n="Confused or lost?">Confused or lost?</h3>
<ul>

View File

@@ -991,7 +991,7 @@ export function getTextGenModel() {
}
export function isJsonSchemaSupported() {
return settings.type === TABBY && main_api === 'textgenerationwebui';
return [TABBY, LLAMACPP].includes(settings.type) && main_api === 'textgenerationwebui';
}
export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {
@@ -1065,7 +1065,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1,
'negative_prompt': cfgValues?.negativePrompt ?? substituteParams(settings.negative_prompt) ?? '',
'grammar_string': settings.grammar_string,
'json_schema': settings.type === TABBY ? settings.json_schema : undefined,
'json_schema': [TABBY, LLAMACPP].includes(settings.type) ? settings.json_schema : undefined,
// llama.cpp aliases. In case someone wants to use LM Studio as Text Completion API
'repeat_penalty': settings.rep_pen,
'tfs_z': settings.tfs,
@@ -1150,5 +1150,15 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
eventSource.emitAndWait(event_types.TEXT_COMPLETION_SETTINGS_READY, params);
// Grammar conflicts with with json_schema
if (settings.type === LLAMACPP) {
if (params.json_schema && Object.keys(params.json_schema).length > 0) {
delete params.grammar_string;
delete params.grammar;
} else {
delete params.json_schema;
}
}
return params;
}

View File

@@ -1,5 +1,5 @@
import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe } from './utils.js';
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean } from './utils.js';
import { extension_settings, getContext } from './extensions.js';
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
import { isMobile } from './RossAscends-mods.js';
@@ -548,6 +548,19 @@ function registerWorldInfoSlashCommands() {
return '';
}
if (typeof newEntryTemplate[field] === 'boolean') {
const isTrue = isTrueBoolean(value);
const isFalse = isFalseBoolean(value);
if (isTrue) {
value = String(true);
}
if (isFalse) {
value = String(false);
}
}
const fuse = new Fuse(entries, {
keys: [{ name: field, weight: 1 }],
includeScore: true,
@@ -1244,6 +1257,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
}
worldEntriesList.sortable({
items: '.world_entry',
delay: getSortableDelay(),
handle: '.drag-handle',
stop: async function (_event, _ui) {