Merge branch 'staging' into pr/2204

This commit is contained in:
steve02081504 2024-05-12 08:51:47 +08:00
commit 78cf6e9086
12 changed files with 1560 additions and 53 deletions

1338
public/global.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@ -1239,7 +1239,7 @@
<input class="neo-range-slider" type="range" id="no_repeat_ngram_size_textgenerationwebui" name="volume" min="0" max="20" step="1">
<input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="no_repeat_ngram_size_textgenerationwebui" id="no_repeat_ngram_size_counter_textgenerationwebui">
</div>
<div data-newbie-hidden data-tg-type="mancer, ooba, dreamgen" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
<div data-newbie-hidden data-tg-type="mancer, ooba, tabby, dreamgen" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
<small data-i18n="Min Length">Min Length</small>
<input class="neo-range-slider" type="range" id="min_length_textgenerationwebui" name="volume" min="0" max="2000" step="1" />
<input class="neo-range-input" type="number" min="0" max="2000" step="1" data-for="min_length_textgenerationwebui" id="min_length_counter_textgenerationwebui">

View File

@ -288,6 +288,11 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
return;
}
// Replace line breaks with <br> in unknown elements
if (node instanceof HTMLUnknownElement) {
node.innerHTML = node.innerHTML.replaceAll('\n', '<br>');
}
const isMediaAllowed = isExternalMediaAllowed();
if (isMediaAllowed) {
return;
@ -458,6 +463,7 @@ let currentVersion = '0.0.0';
export const default_ch_mes = 'Hello';
let generatedPromptCache = '';
let generation_started = new Date();
/** @type {import('scripts/char-data.js').v1CharData[]} */
export let characters = [];
export let this_chid;
let saveCharactersPage = 0;
@ -783,7 +789,6 @@ export let novelai_setting_names;
let abortController;
//css
var css_mes_bg = $('<div class="mes"></div>').css('background');
var css_send_form_display = $('<div id=send_form></div>').css('display');
const MAX_GENERATION_LOOPS = 5;
@ -811,6 +816,28 @@ $.ajaxPrefilter((options, originalOptions, xhr) => {
xhr.setRequestHeader('X-CSRF-Token', token);
});
/**
* Pings the STserver to check if it is reachable.
* @returns {Promise<boolean>} True if the server is reachable, false otherwise.
*/
export async function pingServer() {
try {
const result = await fetch('api/ping', {
method: 'GET',
headers: getRequestHeaders(),
});
if (!result.ok) {
return false;
}
return true;
} catch (error) {
console.error('Error pinging server', error);
return false;
}
}
async function firstLoadInit() {
try {
const tokenResponse = await fetch('/csrf-token');
@ -3077,6 +3104,15 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
}
if (!dryRun) {
// Ping server to make sure it is still alive
const pingResult = await pingServer();
if (!pingResult) {
unblockGeneration(type);
toastr.error('Verify that the server is running and accessible.', 'ST Server cannot be reached' );
throw new Error('Server unreachable');
}
// Hide swipes if not in a dry run.
hideSwipeButtons();
// If generated any message, set the flag to indicate it can't be recreated again.
@ -8580,19 +8616,17 @@ jQuery(async function () {
}
$('.mes').children('.del_checkbox').each(function () {
$(this).prop('checked', false);
$(this).parent().css('background', css_mes_bg);
$(this).parent().removeClass('selected');
});
$(this).css('background', '#600'); //sets the bg of the mes selected for deletion
$(this).addClass('selected'); //sets the bg of the mes selected for deletion
var i = Number($(this).attr('mesid')); //checks the message ID in the chat
this_del_mes = i;
//as long as the current message ID is less than the total chat length
while (i < chat.length) {
//as long as the current message ID is less than the total chat length
$('.mes[mesid=\'' + i + '\']').css('background', '#600'); //sets the bg of the all msgs BELOW the selected .mes
$('.mes[mesid=\'' + i + '\']')
.children('.del_checkbox')
.prop('checked', true);
//sets the bg of the all msgs BELOW the selected .mes
$(`.mes[mesid="${i}"]`).addClass('selected');
$(`.mes[mesid="${i}"]`).children('.del_checkbox').prop('checked', true);
i++;
//console.log(i);
}
});
@ -9196,7 +9230,7 @@ jQuery(async function () {
$('.del_checkbox').each(function () {
$(this).css('display', 'none');
$(this).parent().children('.for_checkbox').css('display', 'block');
$(this).parent().css('background', css_mes_bg);
$(this).parent().removeClass('selected');
$(this).prop('checked', false);
});
showSwipeButtons();
@ -9211,22 +9245,19 @@ jQuery(async function () {
$('.del_checkbox').each(function () {
$(this).css('display', 'none');
$(this).parent().children('.for_checkbox').css('display', 'block');
$(this).parent().css('background', css_mes_bg);
$(this).parent().removeClass('selected');
$(this).prop('checked', false);
});
if (this_del_mes >= 0) {
$('.mes[mesid=\'' + this_del_mes + '\']')
.nextAll('div')
.remove();
$('.mes[mesid=\'' + this_del_mes + '\']').remove();
$(`.mes[mesid="${this_del_mes}"]`).nextAll('div').remove();
$(`.mes[mesid="${this_del_mes}"]`).remove();
chat.length = this_del_mes;
await saveChatConditional();
var $textchat = $('#chat');
$textchat.scrollTop($textchat[0].scrollHeight);
chatElement.scrollTop(chatElement[0].scrollHeight);
eventSource.emit(event_types.MESSAGE_DELETED, chat.length);
$('#chat .mes').removeClass('last_mes');
$('#chat .mes').last().addClass('last_mes');
$('#chat .mes').eq(-2).removeClass('last_mes');
} else {
console.log('this_del_mes is not >= 0, not deleting');
}

View File

@ -0,0 +1,94 @@
/**
* @typedef {object} v2DataWorldInfoEntry
* @property {string[]} keys - An array of primary keys associated with the entry.
* @property {string[]} secondary_keys - An array of secondary keys associated with the entry (optional).
* @property {string} comment - A human-readable description or explanation for the entry.
* @property {string} content - The main content or data associated with the entry.
* @property {boolean} constant - Indicates if the entry's content is fixed and unchangeable.
* @property {boolean} selective - Indicates if the entry's inclusion is controlled by specific conditions.
* @property {number} insertion_order - Defines the order in which the entry is inserted during processing.
* @property {boolean} enabled - Controls whether the entry is currently active and used.
* @property {string} position - Specifies the location or context where the entry applies.
* @property {v2DataWorldInfoEntryExtensionInfos} extensions - An object containing additional details for extensions associated with the entry.
* @property {number} id - A unique identifier assigned to the entry.
*/
/**
* @typedef {object} v2DataWorldInfoEntryExtensionInfos
* @property {number} position - The order in which the extension is applied relative to other extensions.
* @property {boolean} exclude_recursion - Prevents the extension from being applied recursively.
* @property {number} probability - The chance (between 0 and 1) of the extension being applied.
* @property {boolean} useProbability - Determines if the `probability` property is used.
* @property {number} depth - The maximum level of nesting allowed for recursive application of the extension.
* @property {number} selectiveLogic - Defines the logic used to determine if the extension is applied selectively.
* @property {string} group - A category or grouping for the extension.
* @property {boolean} group_override - Overrides any existing group assignment for the extension.
* @property {number} group_weight - A value used for prioritizing extensions within the same group.
* @property {boolean} prevent_recursion - Completely disallows recursive application of the extension.
* @property {number} scan_depth - The maximum depth to search for matches when applying the extension.
* @property {boolean} match_whole_words - Specifies if only entire words should be matched during extension application.
* @property {boolean} use_group_scoring - Indicates if group weight is considered when selecting extensions.
* @property {boolean} case_sensitive - Controls whether case sensitivity is applied during matching for the extension.
* @property {string} automation_id - An identifier used for automation purposes related to the extension.
* @property {number} role - The specific function or purpose of the extension.
* @property {boolean} vectorized - Indicates if the extension is optimized for vectorized processing.
* @property {number} display_index - The order in which the extension should be displayed for user interfaces.
*/
/**
* @typedef {object} v2WorldInfoBook
* @property {string} name - the name of the book
* @property {v2DataWorldInfoEntry[]} entries - the entries of the book
*/
/**
* @typedef {object} v2CharData
* @property {string} name - The character's name.
* @property {string} description - A brief description of the character.
* @property {string} character_version - The character's data version.
* @property {string} personality - A short summary of the character's personality traits.
* @property {string} scenario - A description of the character's background or setting.
* @property {string} first_mes - The character's opening message in a conversation.
* @property {string} mes_example - An example message demonstrating the character's conversation style.
* @property {string} creator_notes - Internal notes or comments left by the character's creator.
* @property {string[]} tags - A list of keywords or labels associated with the character.
* @property {string} system_prompt - The system prompt used to interact with the character.
* @property {string} post_history_instructions - Instructions for handling the character's conversation history.
* @property {string} creator - The name of the person who created the character.
* @property {string[]} alternate_greetings - Additional greeting messages the character can use.
* @property {v2WorldInfoBook} character_book - Data about the character's world or story (if applicable).
* @property {v2CharDataExtensionInfos} extensions - Additional details specific to the character.
*/
/**
* @typedef {object} v2CharDataExtensionInfos
* @property {number} talkativeness - A numerical value indicating the character's propensity to talk.
* @property {boolean} fav - A flag indicating whether the character is a favorite.
* @property {string} world - The fictional world or setting where the character exists (if applicable).
* @property {object} depth_prompt - Prompts used to explore the character's depth and complexity.
* @property {number} depth_prompt.depth - The level of detail or nuance targeted by the prompt.
* @property {string} depth_prompt.prompt - The actual prompt text used for deeper character interaction.
* @property {"system" | "user" | "assistant"} depth_prompt.role - The role the character takes on during the prompted interaction (system, user, or assistant).
* // Non-standard extensions added by external tools
* @property {string} [pygmalion_id] - The unique identifier assigned to the character by the Pygmalion.chat.
* @property {{full_path: string}} [chub] - The Chub-specific data associated with the character.
*/
/**
* @typedef {object} v1CharData
* @property {string} name - the name of the character
* @property {string} description - the description of the character
* @property {string} personality - a short personality description of the character
* @property {string} scenario - a scenario description of the character
* @property {string} first_mes - the first message in the conversation
* @property {string} mes_example - the example message in the conversation
* @property {string} creatorcomment - creator's notes of the character
* @property {string[]} tags - the tags of the character
* @property {number} talkativeness - talkativeness
* @property {boolean|string} fav - fav
* @property {string} create_date - create_date
* @property {v2CharData} data - v2 data extension
* // Non-standard extensions added by the ST server (not part of the original data)
* @property {string} chat - name of the current chat file chat
* @property {string} avatar - file name of the avatar image (acts as a unique identifier)
* @property {string} json_data - the full raw JSON data of the character
*/
export default 0;// now this file is a module

View File

@ -64,10 +64,6 @@
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
</div>
<h3 id="image_list_header">
<strong>Sprite set:</strong>&nbsp;<span id="image_list_header_name"></span>
</h3>
<div id="image_list"></div>
<div class="expression_buttons flex-container spaceEvenly">
<div id="expression_upload_pack_button" class="menu_button">
<i class="fa-solid fa-file-zipper"></i>
@ -80,6 +76,11 @@
</div>
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>/characters/</b> folder of your user data directory and name it as the name of the character.
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
<h3 id="image_list_header">
<strong>Sprite set:</strong>&nbsp;<span id="image_list_header_name"></span>
</h3>
<div id="image_list"></div>
</div>
</div>
</div>

View File

@ -259,6 +259,26 @@ function diceRollReplace(input, invalidRollPlaceholder = '') {
});
}
/**
* Returns the difference between two times. Works with any time format acceptable by moment().
* Can work with {{date}} {{time}} macros
* @param {string} input - The string to replace time difference macros in.
* @returns {string} The string with replaced time difference macros.
*/
function timeDiffReplace(input) {
const timeDiffPattern = /{{timeDiff::(.*?)::(.*?)}}/gi;
const output = input.replace(timeDiffPattern, (_match, matchPart1, matchPart2) => {
const time1 = moment(matchPart1);
const time2 = moment(matchPart2);
const timeDifference = moment.duration(time1.diff(time2));
return timeDifference.humanize();
});
return output;
}
/**
* Substitutes {{macro}} parameters in a string.
* @param {string} content - The string to substitute parameters in.
@ -330,6 +350,7 @@ export function evaluateMacros(content, env) {
const utcTime = moment().utc().utcOffset(utcOffset).format('LT');
return utcTime;
});
content = timeDiffReplace(content);
content = bannedWordsReplace(content);
content = randomReplace(content);
content = pickReplace(content, rawContent);

View File

@ -1865,7 +1865,7 @@ function highlightDefaultContext() {
/**
* Fuzzy search characters by a search term
* @param {string} searchValue - The search term
* @returns {{item?: *, refIndex: number, score: number}[]} Results as items with their score
* @returns {FuseResult[]} Results as items with their score
*/
export function fuzzySearchCharacters(searchValue) {
// @ts-ignore
@ -1898,7 +1898,7 @@ export function fuzzySearchCharacters(searchValue) {
* Fuzzy search world info entries by a search term
* @param {*[]} data - WI items data array
* @param {string} searchValue - The search term
* @returns {{item?: *, refIndex: number, score: number}[]} Results as items with their score
* @returns {FuseResult[]} Results as items with their score
*/
export function fuzzySearchWorldInfo(data, searchValue) {
// @ts-ignore
@ -1927,7 +1927,7 @@ export function fuzzySearchWorldInfo(data, searchValue) {
* Fuzzy search persona entries by a search term
* @param {*[]} data - persona data array
* @param {string} searchValue - The search term
* @returns {{item?: *, refIndex: number, score: number}[]} Results as items with their score
* @returns {FuseResult[]} Results as items with their score
*/
export function fuzzySearchPersonas(data, searchValue) {
data = data.map(x => ({ key: x, name: power_user.personas[x] ?? '', description: power_user.persona_descriptions[x]?.description ?? '' }));
@ -1951,7 +1951,7 @@ export function fuzzySearchPersonas(data, searchValue) {
/**
* Fuzzy search tags by a search term
* @param {string} searchValue - The search term
* @returns {{item?: *, refIndex: number, score: number}[]} Results as items with their score
* @returns {FuseResult[]} Results as items with their score
*/
export function fuzzySearchTags(searchValue) {
// @ts-ignore
@ -1973,7 +1973,7 @@ export function fuzzySearchTags(searchValue) {
/**
* Fuzzy search groups by a search term
* @param {string} searchValue - The search term
* @returns {{item?: *, refIndex: number, score: number}[]} Results as items with their score
* @returns {FuseResult[]} Results as items with their score
*/
export function fuzzySearchGroups(searchValue) {
// @ts-ignore

View File

@ -33,6 +33,7 @@
<li><tt>&lcub;&lcub;isodate&rcub;&rcub;</tt> the current ISO date (YYYY-MM-DD)</li>
<li><tt>&lcub;&lcub;datetimeformat &hellip;&rcub;&rcub;</tt> the current date/time in the specified format, e. g. for German date/time: <tt>&lcub;&lcub;datetimeformat DD.MM.YYYY HH:mm&rcub;&rcub;</tt></li>
<li><tt>&lcub;&lcub;time_UTC±#&rcub;&rcub;</tt> the current time in the specified UTC time zone offset, e.g. UTC-4 or UTC+2</li>
<li><tt>&lcub;&lcub;timeDiff::(time1)::(time2)&rcub;&rcub;</tt> the time difference between time1 and time2. Accepts time and date macros. (Ex: &lcub;&lcub;timeDiff::&lcub;&lcub;isodate&rcub;&rcub; &lcub;&lcub;time&rcub;&rcub;::2024/5/11 12:30:00&rcub;&rcub;)</li>
<li><tt>&lcub;&lcub;idle_duration&rcub;&rcub;</tt> the time since the last user message was sent</li>
<li><tt>&lcub;&lcub;bias "text here"&rcub;&rcub;</tt> sets a behavioral bias for the AI until the next user input. Quotes around the text are important.</li>
<li><tt>&lcub;&lcub;roll:(formula)&rcub;&rcub;</tt> rolls a dice. (ex: <tt>>&lcub;&lcub;roll:1d6&rcub;&rcub;</tt> will roll a 6-sided dice and return a number between 1 and 6)</li>

View File

@ -328,15 +328,20 @@ function getTokenizerForTokenIds() {
}
/**
* @returns {string} String with comma-separated banned token IDs
* @typedef {{banned_tokens: string, banned_strings: string[]}} TokenBanResult
* @returns {TokenBanResult} String with comma-separated banned token IDs
*/
function getCustomTokenBans() {
if (!settings.banned_tokens && !textgenerationwebui_banned_in_macros.length) {
return '';
return {
banned_tokens: '',
banned_strings: [],
};
}
const tokenizer = getTokenizerForTokenIds();
const result = [];
const banned_tokens = [];
const banned_strings = [];
const sequences = settings.banned_tokens
.split('\n')
.concat(textgenerationwebui_banned_in_macros)
@ -358,24 +363,31 @@ function getCustomTokenBans() {
const tokens = JSON.parse(line);
if (Array.isArray(tokens) && tokens.every(t => Number.isInteger(t))) {
result.push(...tokens);
banned_tokens.push(...tokens);
} else {
throw new Error('Not an array of integers');
}
} catch (err) {
console.log(`Failed to parse bad word token list: ${line}`, err);
}
} else if (line.startsWith('"') && line.endsWith('"')) {
// Remove the enclosing quotes
banned_strings.push(line.slice(1, -1))
} else {
try {
const tokens = getTextTokens(tokenizer, line);
result.push(...tokens);
banned_tokens.push(...tokens);
} catch {
console.log(`Could not tokenize raw text: ${line}`);
}
}
}
return result.filter(onlyUnique).map(x => String(x)).join(',');
return {
banned_tokens: banned_tokens.filter(onlyUnique).map(x => String(x)).join(','),
banned_strings: banned_strings,
};
}
/**
@ -697,10 +709,7 @@ jQuery(function () {
$(`#${id}_counter_textgenerationwebui`).val(value);
settings[id] = value;
//special handling for vLLM/Aphrodite using -1 as disabled instead of 0
if ($(this).attr('id') === 'top_k_textgenerationwebui' &&
(settings.type === textgen_types.VLLM ||
settings.type === textgen_types.APHRODITE) &&
value === 0) {
if ($(this).attr('id') === 'top_k_textgenerationwebui' && [INFERMATICAI, APHRODITE, VLLM].includes(settings.type) && value === 0) {
settings[id] = -1;
$(this).val(-1);
}
@ -987,6 +996,8 @@ export function isJsonSchemaSupported() {
export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {
const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet';
const { banned_tokens, banned_strings } = getCustomTokenBans();
let params = {
'prompt': finalPrompt,
'model': getTextGenModel(),
@ -1033,8 +1044,9 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
'mirostat_tau': settings.mirostat_tau,
'mirostat_eta': settings.mirostat_eta,
'custom_token_bans': [APHRODITE, MANCER].includes(settings.type) ?
toIntArray(getCustomTokenBans()) :
getCustomTokenBans(),
toIntArray(banned_tokens) :
banned_tokens,
'banned_strings': banned_strings,
'api_type': settings.type,
'api_server': getTextGenServer(),
'legacy_api': settings.legacy_api && (settings.type === OOBA || settings.type === APHRODITE),

View File

@ -612,8 +612,8 @@ const dateCache = new Map();
/**
* Cached version of moment() to avoid re-parsing the same date strings.
* Important: Moment objects are mutable, so use clone() before modifying them!
* @param {any} timestamp String or number representing a date.
* @returns {*} Moment object
* @param {string|number} timestamp String or number representing a date.
* @returns {moment.Moment} Moment object
*/
export function timestampToMoment(timestamp) {
if (dateCache.has(timestamp)) {
@ -663,8 +663,8 @@ function parseTimestamp(timestamp) {
/**
* Compare two moment objects for sorting.
* @param {*} a The first moment object.
* @param {*} b The second moment object.
* @param {moment.Moment} a The first moment object.
* @param {moment.Moment} b The second moment object.
* @returns {number} A negative number if a is before b, a positive number if a is after b, or 0 if they are equal.
*/
export function sortMoments(a, b) {

View File

@ -813,6 +813,10 @@ body .panelControlBar {
position: relative;
}
#chat .mes.selected{
background-color: rgb(from var(--SmartThemeQuoteColor) r g b / .5);
}
.mes q:before,
.mes q:after {
content: '';

View File

@ -3,7 +3,7 @@ const path = require('path');
const express = require('express');
const fetch = require('node-fetch').default;
const sanitize = require('sanitize-filename');
const { getConfigValue } = require('../util');
const { getConfigValue, color } = require('../util');
const { jsonParser } = require('../express-common');
const writeFileAtomicSync = require('write-file-atomic').sync;
const contentDirectory = path.join(process.cwd(), 'default/content');
@ -94,8 +94,11 @@ function getDefaultPresetFile(filename) {
* @param {ContentItem[]} contentIndex Content index
* @param {import('../users').UserDirectoryList} directories User directories
* @param {string[]} forceCategories List of categories to force check (even if content check is skipped)
* @returns {Promise<boolean>} Whether any content was added
*/
async function seedContentForUser(contentIndex, directories, forceCategories) {
let anyContentAdded = false;
if (!fs.existsSync(directories.root)) {
fs.mkdirSync(directories.root, { recursive: true });
}
@ -134,9 +137,11 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
fs.cpSync(contentPath, targetPath, { recursive: true, force: false });
console.log(`Content file ${contentItem.filename} copied to ${contentTarget}`);
anyContentAdded = true;
}
writeFileAtomicSync(contentLogPath, contentLog.join('\n'));
return anyContentAdded;
}
/**
@ -147,15 +152,27 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
*/
async function checkForNewContent(directoriesList, forceCategories = []) {
try {
if (getConfigValue('skipContentCheck', false) && forceCategories?.length === 0) {
const contentCheckSkip = getConfigValue('skipContentCheck', false);
if (contentCheckSkip && forceCategories?.length === 0) {
return;
}
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8');
const contentIndex = JSON.parse(contentIndexText);
let anyContentAdded = false;
for (const directories of directoriesList) {
await seedContentForUser(contentIndex, directories, forceCategories);
const seedResult = await seedContentForUser(contentIndex, directories, forceCategories);
if (seedResult) {
anyContentAdded = true;
}
}
if (anyContentAdded && !contentCheckSkip && forceCategories?.length === 0) {
console.log();
console.log(`${color.blue('If you don\'t want to receive content updates in the future, set')} ${color.yellow('skipContentCheck')} ${color.blue('to true in the config.yaml file.')}`);
console.log();
}
} catch (err) {
console.log('Content check failed', err);