Merge branch 'staging' into parser-followup-2

This commit is contained in:
LenAnderson 2024-07-28 08:32:25 -04:00
commit 2470f775e2
36 changed files with 490 additions and 265 deletions

View File

@ -93,6 +93,11 @@ openai:
deepl:
# Available options: default, more, less, prefer_more, prefer_less
formality: default
# -- MISTRAL API CONFIGURATION --
mistral:
# Enables prefilling of the reply with the last assistant message in the prompt
# CAUTION: The prefix is echoed into the completion. You may want to use regex to trim it out.
enablePrefix: false
# -- SERVER PLUGIN CONFIGURATION --
enableServerPlugins: false
# User session timeout *in seconds* (defaults to 24 hours).

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "sillytavern",
"version": "1.12.3",
"version": "1.12.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
"version": "1.12.3",
"version": "1.12.4",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {

View File

@ -70,7 +70,7 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.12.3",
"version": "1.12.4",
"scripts": {
"start": "node server.js",
"start:no-csrf": "node server.js --disableCsrf",

View File

@ -89,7 +89,7 @@
position: absolute;
width: 100%;
height: 100vh;
height: 100svh;
height: 100dvh;
z-index: 9998;
top: 0;
}

View File

@ -7,7 +7,8 @@
background-color: green;
}
.extensions_block input[type="checkbox"] {
.extensions_block input[type="checkbox"],
.extensions_block input[type="radio"] {
margin-left: 10px;
margin-right: 10px;
}

View File

@ -7,8 +7,8 @@
z-index: 999999;
width: 100vw;
height: 100vh;
width: 100svw;
height: 100svh;
width: 100dvw;
height: 100dvh;
background-color: var(--SmartThemeBlurTintColor);
color: var(--SmartThemeBodyColor);
/*for some reason the full screen blur does not work on iOS*/

View File

@ -1,7 +1,7 @@
#logprobsViewer {
overflow-y: auto;
max-width: 90svw;
max-height: 90svh;
max-width: 90dvw;
max-height: 90dvh;
min-width: 100px;
min-height: 50px;
border-radius: 10px;
@ -16,7 +16,7 @@
top: 0;
margin: 0;
right: unset;
width: calc(((100svw - var(--sheldWidth)) / 2) - 1px);
width: calc(((100dvw - var(--sheldWidth)) / 2) - 1px);
}
.logprobs_panel_header {

View File

@ -1,6 +1,8 @@
/*will apply to anything 1000px or less. this catches ipads, horizontal phones, and vertical phones)*/
@media screen and (max-width: 1000px) {
#send_form.compact #leftSendForm, #send_form.compact #rightSendForm {
#send_form.compact #leftSendForm,
#send_form.compact #rightSendForm {
flex-wrap: nowrap;
width: unset;
}
@ -34,9 +36,9 @@
right: 0;
width: fit-content;
max-height: calc(60vh - 60px);
max-height: calc(60svh - 60px);
max-height: calc(60dvh - 60px);
max-width: 90vw;
max-width: 90svw;
max-width: 90dvw;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
@ -102,7 +104,7 @@
min-width: unset;
width: 100%;
max-height: calc(100vh - 45px);
max-height: calc(100svh - 45px);
max-height: calc(100dvh - 45px);
position: fixed;
left: 0;
top: 5px;
@ -130,15 +132,15 @@
#top-bar {
position: fixed;
width: 100vw;
width: 100svw;
width: 100dvw;
}
#bg1,
#bg_custom {
height: 100vh !important;
height: 100svh !important;
height: 100dvh !important;
width: 100vw !important;
width: 100svw !important;
width: 100dvw !important;
background-position: center;
}
@ -146,13 +148,7 @@
#sheld,
#character_popup,
.drawer-content
/* ,
#world_popup */
{
/*max-height: calc(100vh - 36px);
max-height: calc(100svh - 36px);*/
.drawer-content {
width: 100% !important;
margin: 0 auto;
max-width: 100%;
@ -223,10 +219,10 @@
#floatingPrompt,
#cfgConfig,
#logprobsViewer,
#movingDivs > div {
/* 100vh are fallback units for browsers that don't support svh */
#movingDivs>div {
/* 100vh are fallback units for browsers that don't support dvh */
height: calc(100vh - 45px);
height: calc(100svh - 45px);
height: calc(100dvh - 45px);
min-width: 100% !important;
width: 100% !important;
max-width: 100% !important;
@ -249,7 +245,7 @@
#floatingPrompt,
#cfgConfig,
#logprobsViewer,
#movingDivs > div {
#movingDivs>div {
height: min-content;
}
@ -286,9 +282,9 @@
body.waifuMode #sheld {
height: 40vh;
height: 40svh;
height: 40dvh;
top: 60vh;
top: 60svh;
top: 60dvh;
bottom: 0 !important;
}
@ -325,16 +321,16 @@
body.waifuMode .zoomed_avatar {
width: fit-content;
max-height: calc(60vh - 60px);
max-height: calc(60svh - 60px);
max-height: calc(60dvh - 60px);
max-width: 90vw;
max-width: 90svw;
max-width: 90dvw;
}
.scrollableInner {
overflow-y: auto;
overflow-x: hidden;
max-height: calc(100vh - 90px);
max-height: calc(100svh - 90px);
max-height: calc(100dvh - 90px);
}
.horde_multiple_hint {
@ -370,9 +366,9 @@
body:not(.waifuMode) .zoomed_avatar {
max-height: calc(60vh - 60px);
max-height: calc(60svh - 60px);
max-height: calc(60dvh - 60px);
max-width: 90vw;
max-width: 90svw;
max-width: 90dvw;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
@ -453,9 +449,9 @@
min-height: unset;
max-height: unset;
width: 100vw;
width: 100svw;
width: 100dvw;
height: calc(100vh - 36px);
height: calc(100svh - 36px);
height: calc(100dvh - 36px);
padding-right: max(env(safe-area-inset-right), 0px);
padding-left: max(env(safe-area-inset-left), 0px);
padding-bottom: 0;
@ -485,10 +481,10 @@
top: 0;
margin: 0 auto;
height: calc(100vh - 70px);
height: calc(100svh - 70px);
height: calc(100dvh - 70px);
width: calc(100% - 5px);
max-height: calc(100vh - 70px);
max-height: calc(100svh - 70px);
max-height: calc(100dvh - 70px);
max-width: calc(100% - 5px);
}

View File

@ -7,5 +7,5 @@ body.safari .popup.large_dialogue_popup .popup-body {
body.safari .popup .popup-body {
height: fit-content;
max-height: 90vh;
max-height: 90svh;
max-height: 90dvh;
}

View File

@ -16,8 +16,8 @@ dialog {
display: flex;
flex-direction: column;
max-height: calc(100svh - 2em);
max-width: calc(100svw - 2em);
max-height: calc(100dvh - 2em);
max-width: calc(100dvw - 2em);
min-height: fit-content;
/* Overflow visible so elements (like toasts) can appear outside of the dialog. '.popup-body' is hiding overflow for the real content. */
@ -103,7 +103,7 @@ body.no-blur .popup[open]::backdrop {
.popup #toast-container {
/* Fix toastr in dialogs by actually placing it at the top of the screen via transform */
height: 100svh;
height: 100dvh;
top: calc(50% + var(--topBarBlockSize));
left: 50%;
transform: translate(-50%, -50%);
@ -115,7 +115,7 @@ body.no-blur .popup[open]::backdrop {
.popup-crop-wrap {
margin: 10px auto;
max-height: 75vh;
max-height: 75svh;
max-height: 75dvh;
max-width: 100%;
}

View File

@ -364,7 +364,7 @@ body.waifuMode #top-bar {
body.waifuMode #sheld {
height: 40vh;
height: 40svh;
height: 40dvh;
top: calc(100% - 40vh);
bottom: 0;
}

2
public/global.d.ts vendored
View File

@ -16,6 +16,8 @@ declare var ai;
// Jquery plugins
interface JQuery {
nanogallery2(options?: any): JQuery;
nanogallery2(method: string, options?: any): JQuery;
pagination(method: 'getCurrentPageNum'): number;
pagination(method: string, options?: any): JQuery;
pagination(options?: any): JQuery;

View File

@ -3296,6 +3296,24 @@
</div>
</div>
</div>
<div class="flex-container">
<div class="flex1" title="Inserted before the first User's message." data-i18n="[title]Inserted before the first User's message.">
<label for="instruct_first_input_sequence">
<small data-i18n="First User Prefix">First User Prefix</small>
</label>
<div>
<textarea id="instruct_first_input_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
<div class="flex1" title="Inserted before the last User's message." data-i18n="[title]instruct_last_input_sequence">
<label for="instruct_last_input_sequence">
<small data-i18n="Last User Prefix">Last User Prefix</small>
</label>
<div>
<textarea id="instruct_last_input_sequence" class="text_pole wide100p textarea_compact autoSetHeight" maxlength="2000" placeholder="&mdash;" rows="1"></textarea>
</div>
</div>
</div>
<div class="flex-container">
<div class="flex1" title="Will be inserted as a last prompt line when using system/neutral generation." data-i18n="[title]Will be inserted as a last prompt line when using system/neutral generation.">
<label for="instruct_last_system_sequence">
@ -3644,8 +3662,8 @@
<div id="OpenAllWIEntries" class="menu_button fa-solid fa-expand" title="Open all Entries" data-i18n="[title]Open all Entries"></div>
<div id="CloseAllWIEntries" class="menu_button fa-solid fa-compress" title="Close all Entries" data-i18n="[title]Close all Entries"></div>
<div id="world_popup_new" class="menu_button fa-solid fa-plus" title="New Entry" data-i18n="[title]New Entry"></div>
<div id="world_backfill_memos" class="menu_button fa-solid fa-notes-medical" title="Fill empty Memo/Titles with Keywords" data-i18n="[title]Fill empty Memo/Titles with Keywords"></div><div id="world_apply_custom_sorting" class="menu_button fa-solid fa-solid fa-arrow-down-9-1"
title="Apply custom sorting as Order" data-i18n="[title]Apply custom sorting as Order"></div>
<div id="world_backfill_memos" class="menu_button fa-solid fa-notes-medical" title="Fill empty Memo/Titles with Keywords" data-i18n="[title]Fill empty Memo/Titles with Keywords"></div>
<div id="world_apply_current_sorting" class="menu_button fa-solid fa-solid fa-arrow-down-9-1" title="Apply current sorting as Order" data-i18n="[title]Apply current sorting as Order"></div>
<div id="world_import_button" class="menu_button fa-solid fa-file-import" title="Import World Info" data-i18n="[title]Import World Info"></div>
<div id="world_popup_export" class="menu_button fa-solid fa-file-export" title="Export World Info" data-i18n="[title]Export World Info"></div>
<div id="world_duplicate" class="menu_button fa-solid fa-paste" title="Duplicate World Info" data-i18n="[title]Duplicate World Info"></div>
@ -4008,7 +4026,7 @@
<input type="range" id="smooth_streaming_speed" name="smooth_streaming_speed" min="0" max="100" step="10" value="50">
<div class="slider_hint">
<span data-i18n="Slow">Slow</span>
<span data-i18n=""></span>
<span></span>
<span data-i18n="Fast">Fast</span>
</div>
</div>
@ -6450,6 +6468,9 @@
<button class="menu_button set_default_persona" title="Select this as default persona for the new chats." data-i18n="[title]Select this as default persona for the new chats.">
<i class="fa-fw fa-solid fa-crown"></i>
</button>
<button class="menu_button duplicate_persona" title="Duplicate persona" data-i18n="[title]Duplicate persona">
<i class="fa-fw fa-solid fa-clone"></i>
</button>
<button class="menu_button delete_avatar" title="Delete persona" data-i18n="[title]Delete persona">
<i class="fa-fw fa-solid fa-trash-alt"></i>
</button>

View File

@ -217,9 +217,11 @@
"Character Names Behavior": "角色名称行为",
"Helps the model to associate messages with characters.": "有助于模型将消息与角色关联起来。",
"None": "无",
"character_names_none": "不添加角色名称前缀。在群聊中可能导致错误行为,谨慎勾选。",
"Never add character names.": "不添加角色名称。",
"character_names_default": "群聊和过去的角色除外。否则,请确保在提示词中提供了姓名。",
"Don't add character names.": "不添加角色名称。",
"Completion": "补全对象",
"Don't add character names unless necessary.": "如非必要,否则不添加角色名称。",
"Completion Object": "补全对象",
"character_names_completion": "适用限制仅限拉丁字母数字和下划线。不适用于所有补全源尤其是Claude、MistralAI、Google。",
"Add character names to completion objects.": "在补全对象中添加角色名称。",
"Message Content": "消息内容",
@ -489,6 +491,10 @@
"First Assistant Prefix": "第一个助理前缀",
"instruct_last_output_sequence": "插入到最后一条助手消息之前或作为生成 AI 回复时的最后一行提示词(中立/系统角色除外)。",
"Last Assistant Prefix": "最后一个助理前缀",
"Inserted before the first User's message.": "插入在第一个用户的消息之前。",
"First User Prefix": "第一个用户前缀",
"instruct_last_input_sequence": "插入到最后一条用户消息之前。",
"Last User Prefix": "上次用户前缀",
"Will be inserted as a last prompt line when using system/neutral generation.": "当使用系统/中性生成时将作为最后的一行提示词插入。",
"System Instruction Prefix": "系统指令前缀",
"If a stop sequence is generated, everything past it will be removed from the output (inclusive).": "如果生成了停止序列,则该序列之后的所有内容都将从输出中删除(包括在内)。",
@ -555,7 +561,7 @@
"Close all Entries": "关闭所有条目",
"New Entry": "新条目",
"Fill empty Memo/Titles with Keywords": "使用关键字填充空的备忘录/标题",
"Apply custom sorting as Order": "应用自定义排序作为顺序",
"Apply current sorting as Order": "应用当前排序作为顺序",
"Import World Info": "导入世界书",
"Export World Info": "导出世界书",
"Duplicate World Info": "复制世界书",
@ -1187,6 +1193,7 @@
"Bind user name to that avatar": "将用户名称绑定到该头像",
"Change persona image": "更改用户角色头像",
"Select this as default persona for the new chats.": "选择此项作为新聊天的默认用户角色。",
"Duplicate persona": "复制用户角色",
"Delete persona": "删除用户角色",
"These characters are the winners of character design contests and have outstandable quality.": "这些角色都是角色设计大赛的获奖者,品质非常出色。",
"Contest Winners": "比赛获胜者",
@ -1327,6 +1334,10 @@
"ext_sum_injection_template": "插入模板",
"ext_sum_memory_template_placeholder": "{{summary}} 将解析当前摘要内容。",
"ext_sum_injection_position": "插入位置",
"ext_sum_include_wi_scan_desc": "在 WI 扫描中包括最新摘要。",
"ext_sum_include_wi_scan": "纳入世界信息扫描",
"None (not injected)": "无(未注入)",
"ext_sum_injection_position_none": "摘要不会被注入到提示中。您仍然可以通过 {{summary}} 宏访问它。",
"How many messages before the current end of the chat.": "当前聊天结束前还有多少条消息。",
"Labels and Message": "标签和信息",
"Label": "标签",
@ -1757,6 +1768,8 @@
"help_macros_56": "指示系统指令前缀",
"help_macros_57": "指示第一个用户消息填充器",
"help_macros_58": "指示停止顺序",
"help_macros_first_user": "指示用户第一个输入序列",
"help_macros_last_user": "指示用户最后输入序列",
"Chat variables Macros:": "聊天变量宏:",
"Local variables = unique to the current chat": "局部变量 = 当前聊天所独有",
"Global variables = works in any chat for any character": "全局变量 = 适用于任何角色的任何聊天",
@ -1794,6 +1807,7 @@
"Record a snapshot of your current settings.": "记录当前设置的快照。",
"Make a Snapshot": "制作快照",
"Restore this snapshot": "恢复此快照",
"Download Model": "下载模型",
"Downloader Options": "下载器选项",
"Extra parameters for downloading/HuggingFace API": "下载/HuggingFace API 的额外参数。如果不确定,请将其留空。",
"Revision": "修订",

View File

@ -570,6 +570,7 @@ export const system_message_types = {
* @enum {number} Extension prompt types
*/
export const extension_prompt_types = {
NONE: -1,
IN_PROMPT: 0,
IN_CHAT: 1,
BEFORE_PROMPT: 2,
@ -929,10 +930,17 @@ async function firstLoadInit() {
initCfg();
initLogprobs();
doDailyExtensionUpdatesCheck();
hideLoader();
await hideLoader();
await fixViewport();
await eventSource.emit(event_types.APP_READY);
}
async function fixViewport() {
document.body.style.position = 'absolute';
await delay(1);
document.body.style.position = '';
}
function cancelStatusCheck() {
abortStatusCheck?.abort();
abortStatusCheck = new AbortController();
@ -3611,6 +3619,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
let chat2 = [];
let continue_mag = '';
const userMessageIndices = [];
const lastUserMessageIndex = coreChat.findLastIndex(x => x.is_user);
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
if (main_api == 'openai') {
@ -3629,6 +3638,11 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST);
}
if (lastUserMessageIndex >= 0 && j === lastUserMessageIndex && isInstruct) {
// Reformat with the last input sequence (if any)
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST);
}
// Do not suffix the message for continuation
if (i === 0 && isContinue) {
if (isInstruct) {
@ -3654,7 +3668,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
mes: power_user.instruct.user_alignment_message,
is_user: true,
};
userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, false);
userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, force_output_sequence.FIRST);
}
// Call combined AN into Generate
@ -6053,9 +6067,10 @@ async function getChatResult() {
const message = getFirstMessage();
if (message.mes) {
chat.push(message);
await saveChatConditional();
freshChat = true;
}
// Make sure the chat appears on the server
await saveChatConditional();
}
await loadItemizedPrompts(getCurrentChatId());
await printMessages();
@ -6107,7 +6122,7 @@ export async function openCharacterChat(file_name) {
chat_metadata = {};
await getChat();
$('#selected_chat_pole').val(file_name);
await createOrEditCharacter();
await createOrEditCharacter(new CustomEvent('newChat'));
}
////////// OPTIMZED MAIN API CHANGE FUNCTION ////////////
@ -6809,6 +6824,11 @@ export async function displayPastChats() {
const fileName = chat['file_name'];
const chatContent = rawChats[fileName];
// Make sure empty chats are displayed when there is no search query
if (Array.isArray(chatContent) && !chatContent.length && !searchQuery) {
return true;
}
// // Uncomment this to return to old behavior (classical full-substring search).
// return chatContent && Object.values(chatContent).some(message => message?.mes?.toLowerCase()?.includes(searchQuery.toLowerCase()));
@ -8436,6 +8456,11 @@ const CONNECT_API_MAP = {
button: '#api_button_textgenerationwebui',
type: textgen_types.OPENROUTER,
},
'featherless': {
selected: 'textgenerationwebui',
button: '#api_button_textgenerationwebui',
type: textgen_types.FEATHERLESS,
},
'huggingface': {
selected: 'textgenerationwebui',
button: '#api_button_textgenerationwebui',
@ -9837,8 +9862,8 @@ jQuery(async function () {
hideMenu();
});
$('#newChatFromManageScreenButton').on('click', function () {
doNewChat({ deleteCurrentChat: false });
$('#newChatFromManageScreenButton').on('click', async function () {
await doNewChat({ deleteCurrentChat: false });
$('#select_chat_cross').trigger('click');
});
@ -10810,7 +10835,7 @@ jQuery(async function () {
//newSlider.val(manualInput)
//handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual');
valueBeforeManualInput = manualInput;
$(masterElement).val($(this).val()).trigger('input');
$(masterElement).val($(this).val()).trigger('input', { forced: true });
} else {
//if value not ok, warn and reset to last known valid value
toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`);
@ -10836,7 +10861,7 @@ jQuery(async function () {
if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) {
valueBeforeManualInput = manualInput;
//set the slider value to input value
$(masterElement).val($(this).val()).trigger('input');
$(masterElement).val($(this).val()).trigger('input', { forced: true });
} else {
//if value not ok, warn and reset to last known valid value
toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`);

View File

@ -1387,7 +1387,8 @@ async function getExpressionsList() {
}
// If there was no specific list, or an error, just return the default expressions
return DEFAULT_EXPRESSIONS;
expressionsList = DEFAULT_EXPRESSIONS.filter(e => e !== 'talkinghead').slice();
return expressionsList;
}
const result = await resolveExpressionsList();
@ -1618,11 +1619,13 @@ async function onClickExpressionRemoveCustom() {
moduleWorker();
}
function onExperesionApiChanged() {
function onExpressionApiChanged() {
const tempApi = this.value;
if (tempApi) {
extension_settings.expressions.api = Number(tempApi);
$('.expression_llm_prompt_block').toggle(extension_settings.expressions.api === EXPRESSION_API.llm);
expressionsList = null;
spriteCache = {};
moduleWorker();
saveSettingsDebounced();
}
@ -1972,7 +1975,7 @@ function migrateSettings() {
$('#expression_custom_add').on('click', onClickExpressionAddCustom);
$('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
$('#expression_fallback').on('change', onExpressionFallbackChanged);
$('#expression_api').on('change', onExperesionApiChanged);
$('#expression_api').on('change', onExpressionApiChanged);
}
// Pause Talkinghead to save resources when the ST tab is not visible or the window is minimized.

View File

@ -3,6 +3,7 @@ import {
this_chid,
characters,
getRequestHeaders,
event_types,
} from '../../../script.js';
import { groups, selected_group } from '../../group-chats.js';
import { loadFileToDocument, delay } from '../../utils.js';
@ -25,6 +26,27 @@ let paginationVisiblePages = 10;
let paginationMaxLinesPerPage = 2;
let galleryMaxRows = 3;
$('body').on('click', '.dragClose', function () {
const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
$(`body > .draggable[id="${relatedId}"]`).remove(); // Remove the associated draggable
});
const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved';
const mutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.removedNodes.forEach((node) => {
if (node instanceof HTMLElement && node.tagName === 'DIV' && node.id === 'gallery') {
eventSource.emit(CUSTOM_GALLERY_REMOVED_EVENT);
}
});
});
});
mutationObserver.observe(document.body, {
childList: true,
subtree: false,
});
/**
* Retrieves a list of gallery items based on a given URL. This function calls an API endpoint
@ -59,7 +81,9 @@ async function getGalleryItems(url) {
* @returns {Promise<void>} - Promise representing the completion of the gallery initialization.
*/
async function initGallery(items, url) {
const nonce = `nonce-${Math.random().toString(36).substring(2, 15)}`;
const gallery = $('#dragGallery');
gallery.addClass(nonce);
gallery.nanogallery2({
'items': items,
thumbnailWidth: 'auto',
@ -82,16 +106,26 @@ async function initGallery(items, url) {
fnThumbnailOpen: viewWithDragbox,
});
eventSource.on('resizeUI', function (elmntName) {
gallery.nanogallery2('resize');
});
const dragDropHandler = new DragAndDropHandler('#dragGallery', async (files, event) => {
const dragDropHandler = new DragAndDropHandler(`#dragGallery.${nonce}`, async (files, event) => {
let file = files[0];
uploadFile(file, url); // Added url parameter to know where to upload
});
const resizeHandler = function () {
gallery.nanogallery2('resize');
};
eventSource.on('resizeUI', resizeHandler);
eventSource.once(event_types.CHAT_CHANGED, function () {
gallery.closest('#gallery').remove();
});
eventSource.once(CUSTOM_GALLERY_REMOVED_EVENT, function () {
gallery.nanogallery2('destroy');
dragDropHandler.destroy();
eventSource.removeListener('resizeUI', resizeHandler);
});
// Set dropzone height to be the same as the parent
gallery.css('height', gallery.parent().css('height'));
@ -140,16 +174,10 @@ async function showCharGallery() {
const items = await getGalleryItems(url);
// if there already is a gallery, destroy it and place this one in its place
if ($('#dragGallery').length) {
$('#dragGallery').nanogallery2('destroy');
initGallery(items, url);
} else {
makeMovable();
setTimeout(async () => {
await initGallery(items, url);
}, 100);
}
$('#dragGallery').closest('#gallery').remove();
makeMovable();
await delay(100);
await initGallery(items, url);
} catch (err) {
console.trace();
console.error(err);
@ -202,11 +230,11 @@ async function uploadFile(file, url) {
toastr.success('File uploaded successfully. Saved at: ' + result.path);
// Refresh the gallery
$('#dragGallery').nanogallery2('destroy'); // Destroy old gallery
const newItems = await getGalleryItems(url); // Fetch the latest items
initGallery(newItems, url); // Reinitialize the gallery with new items and pass 'url'
$('#dragGallery').closest('#gallery').remove(); // Destroy old gallery
makeMovable();
await delay(100);
await initGallery(newItems, url); // Reinitialize the gallery with new items and pass 'url'
} catch (error) {
console.error('There was an issue uploading the file:', error);
@ -273,11 +301,6 @@ function makeMovable(id = 'gallery') {
e.preventDefault();
return false;
});
$('body').on('click', '.dragClose', function () {
const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
$(`#${relatedId}`).remove(); // Remove the associated draggable
});
}
/**
@ -358,11 +381,6 @@ function makeDragImg(id, url) {
} else {
console.error('Failed to append the template content or retrieve the appended content.');
}
$('body').on('click', '.dragClose', function () {
const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
$(`#${relatedId}`).remove(); // Remove the associated draggable
});
}
/**
@ -401,7 +419,8 @@ function viewWithDragbox(items) {
// Registers a simple command for opening the char gallery.
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'show-gallery',
aliases: ['sg'],
callback: () => {
showCharGallery();
@ -409,7 +428,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery
},
helpString: 'Shows the gallery.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery',
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'list-gallery',
aliases: ['lg'],
callback: listGalleryCommand,
returns: 'list of images',
@ -432,14 +452,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery
async function listGalleryCommand(args) {
try {
let url = args.char ?? (args.group ? groups.find(it=>it.name == args.group)?.id : null) ?? (selected_group || this_chid);
let url = args.char ?? (args.group ? groups.find(it => it.name == args.group)?.id : null) ?? (selected_group || this_chid);
if (!args.char && !args.group && !selected_group && this_chid) {
const char = characters[this_chid];
url = char.avatar.replace('.png', '');
}
const items = await getGalleryItems(url);
return JSON.stringify(items.map(it=>it.src));
return JSON.stringify(items.map(it => it.src));
} catch (err) {
console.trace();

View File

@ -5,7 +5,7 @@
"optional": [
],
"js": "index.js",
"css": "",
"css": "style.css",
"author": "City-Unit",
"version": "1.5.0",
"homePage": "https://github.com/SillyTavern/SillyTavern"

View File

@ -0,0 +1,5 @@
.nGY2 .nGY2GalleryBottom {
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -14,6 +14,7 @@ import {
substituteParamsExtended,
generateRaw,
getMaxContextSize,
setExtensionPrompt,
} from '../../../script.js';
import { is_group_generating, selected_group } from '../../group-chats.js';
import { loadMovingUIState } from '../../power-user.js';
@ -73,6 +74,7 @@ const defaultSettings = {
template: defaultTemplate,
position: extension_prompt_types.IN_PROMPT,
role: extension_prompt_roles.SYSTEM,
scan: false,
depth: 2,
promptWords: 200,
promptMinWords: 25,
@ -122,6 +124,7 @@ function loadSettings() {
$(`input[name="memory_prompt_builder"][value="${extension_settings.memory.prompt_builder}"]`).prop('checked', true).trigger('input');
$('#memory_override_response_length').val(extension_settings.memory.overrideResponseLength).trigger('input');
$('#memory_max_messages_per_request').val(extension_settings.memory.maxMessagesPerRequest).trigger('input');
$('#memory_include_wi_scan').prop('checked', extension_settings.memory.scan).trigger('input');
switchSourceControls(extension_settings.memory.source);
}
@ -279,6 +282,13 @@ function onMemoryPositionChange(e) {
saveSettingsDebounced();
}
function onMemoryIncludeWIScanInput() {
const value = !!$(this).prop('checked');
extension_settings.memory.scan = value;
reinsertMemory();
saveSettingsDebounced();
}
function onMemoryPromptWordsForceInput() {
const value = $(this).val();
extension_settings.memory.promptForceWords = Number(value);
@ -800,8 +810,7 @@ function reinsertMemory() {
* @param {number|null} index Index of the chat message to save the summary to. If null, the pre-last message is used.
*/
function setMemoryContext(value, saveToMessage, index = null) {
const context = getContext();
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, false, extension_settings.memory.role);
setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, extension_settings.memory.scan, extension_settings.memory.role);
$('#memory_contents').val(value);
const summaryLog = value
@ -809,6 +818,7 @@ function setMemoryContext(value, saveToMessage, index = null) {
: 'Summary has no content';
console.debug(summaryLog);
const context = getContext();
if (saveToMessage && context.chat.length) {
const idx = index ?? context.chat.length - 2;
const mes = context.chat[idx < 0 ? 0 : idx];
@ -894,6 +904,7 @@ function setupListeners() {
$('#memory_prompt_words_auto').off('click').on('click', onPromptForceWordsAutoClick);
$('#memory_override_response_length').off('click').on('input', onOverrideResponseLengthInput);
$('#memory_max_messages_per_request').off('click').on('input', onMaxMessagesPerRequestInput);
$('#memory_include_wi_scan').off('input').on('input', onMemoryIncludeWIScanInput);
$('#summarySettingsBlockToggle').off('click').on('click', function () {
console.log('saw settings button click');
$('#summarySettingsBlock').slideToggle(200, 'swing'); //toggleClass("hidden");

View File

@ -109,7 +109,16 @@
<textarea id="memory_template" class="text_pole textarea_compact" rows="2" data-i18n="[placeholder]ext_sum_memory_template_placeholder" placeholder="&lcub;&lcub;summary&rcub;&rcub; will resolve to the current summary contents."></textarea>
</div>
<label for="memory_position" data-i18n="ext_sum_injection_position">Injection Position</label>
<label class="checkbox_label" for="memory_include_wi_scan" data-i18n="[title]ext_sum_include_wi_scan_desc" title="Include the latest summary in the WI scan.">
<input id="memory_include_wi_scan" type="checkbox" />
<span data-i18n="ext_sum_include_wi_scan">Include in World Info Scanning</span>
</label>
<div class="radio_group">
<label>
<input type="radio" name="memory_position" value="-1" />
<span data-i18n="None (not injected)">None (not injected)</span>
<i class="fa-solid fa-info-circle" title="The summary will not be injected into the prompt. You can still access it via the &lcub;&lcub;summary&rcub;&rcub; macro." data-i18n="[title]ext_sum_injection_position_none"></i>
</label>
<label>
<input type="radio" name="memory_position" value="2" />
<span data-i18n="Before Main Prompt / Story String">Before Main Prompt / Story String</span>

View File

@ -335,8 +335,8 @@
flex-direction: column;
}
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
min-height: 50svh;
height: 50svh;
min-height: 50dvh;
height: 50dvh;
}
}
.popup:has(#qr--modalEditor) {

View File

@ -395,8 +395,8 @@
}
>#qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder {
min-height: 50svh;
height: 50svh;
min-height: 50dvh;
height: 50dvh;
}
}
}

View File

@ -34,6 +34,8 @@ const controls = [
{ id: 'instruct_names_force_groups', property: 'names_force_groups', isCheckbox: true },
{ id: 'instruct_first_output_sequence', property: 'first_output_sequence', isCheckbox: false },
{ id: 'instruct_last_output_sequence', property: 'last_output_sequence', isCheckbox: false },
{ id: 'instruct_first_input_sequence', property: 'first_input_sequence', isCheckbox: false },
{ id: 'instruct_last_input_sequence', property: 'last_input_sequence', isCheckbox: false },
{ id: 'instruct_activation_regex', property: 'activation_regex', isCheckbox: false },
{ id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },
{ id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },
@ -58,6 +60,8 @@ function migrateInstructModeSettings(settings) {
system_suffix: '',
user_alignment_message: '',
last_system_sequence: '',
first_input_sequence: '',
last_input_sequence: '',
names_force_groups: true,
skip_examples: false,
system_same_as_user: false,
@ -253,7 +257,15 @@ export function getInstructStoppingSequences() {
const system_sequence = power_user.instruct.system_sequence?.replace(/{{name}}/gi, 'System') || '';
const last_system_sequence = power_user.instruct.last_system_sequence?.replace(/{{name}}/gi, 'System') || '';
const combined_sequence = `${stop_sequence}\n${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}\n${system_sequence}\n${last_system_sequence}`;
const combined_sequence = [
stop_sequence,
input_sequence,
output_sequence,
first_output_sequence,
last_output_sequence,
system_sequence,
last_system_sequence,
].join('\n');
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
}
@ -301,6 +313,14 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
}
if (isUser) {
if (forceOutputSequence === force_output_sequence.FIRST) {
return power_user.instruct.first_input_sequence || power_user.instruct.input_sequence;
}
if (forceOutputSequence === force_output_sequence.LAST) {
return power_user.instruct.last_input_sequence || power_user.instruct.input_sequence;
}
return power_user.instruct.input_sequence;
}
@ -552,6 +572,8 @@ export function replaceInstructMacros(input, env) {
'instructStop': power_user.instruct.stop_sequence,
'instructUserFiller': power_user.instruct.user_alignment_message,
'instructSystemInstructionPrefix': power_user.instruct.last_system_sequence,
'instructFirstInput|instructFirstUserPrefix': power_user.instruct.first_input_sequence || power_user.instruct.input_sequence,
'instructLastInput|instructLastUserPrefix': power_user.instruct.last_input_sequence || power_user.instruct.input_sequence,
};
for (const [placeholder, value] of Object.entries(instructMacros)) {

View File

@ -1,5 +1,4 @@
import {
callPopup,
characters,
chat,
chat_metadata,
@ -22,7 +21,7 @@ import { PAGINATION_TEMPLATE, debounce, delay, download, ensureImageFormatSuppor
import { debounce_timeout } from './constants.js';
import { FILTER_TYPES, FilterHelper } from './filters.js';
import { selected_group } from './group-chats.js';
import { POPUP_TYPE, Popup } from './popup.js';
import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
let savePersonasPage = 0;
const GRID_STORAGE_KEY = 'Personas_GridView';
@ -332,15 +331,14 @@ async function changeUserAvatar(e) {
* @returns {Promise} Promise that resolves when the persona is set
*/
export async function createPersona(avatarId) {
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>Cancel if you\'re just uploading an avatar.', 'input', '');
const personaName = await Popup.show.input('Enter a name for this persona:', 'Cancel if you\'re just uploading an avatar.', '');
if (!personaName) {
console.debug('User cancelled creating a persona');
return;
}
await delay(500);
const personaDescription = await callPopup('<h3>Enter a description for this persona:</h3>You can always add or change it later.', 'input', '', { rows: 4 });
const personaDescription = await Popup.show.input('Enter a description for this persona:', 'You can always add or change it later.', '', { rows: 4 });
initPersona(avatarId, personaName, personaDescription);
if (power_user.persona_show_notifications) {
@ -349,7 +347,7 @@ export async function createPersona(avatarId) {
}
async function createDummyPersona() {
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>', 'input', '');
const personaName = await Popup.show.input('Enter a name for this persona:', null);
if (!personaName) {
console.debug('User cancelled creating dummy persona');
@ -508,15 +506,20 @@ async function bindUserNameToPersona(e) {
return;
}
let personaUnbind = false;
const existingPersona = power_user.personas[avatarId];
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>(If empty name is provided, this will unbind the name from this avatar)', 'input', existingPersona || '');
const personaName = await Popup.show.input(
'Enter a name for this persona:',
'(If empty name is provided, this will unbind the name from this avatar)',
existingPersona || '',
{ onClose: (p) => { personaUnbind = p.value === '' && p.result === POPUP_RESULT.AFFIRMATIVE; } });
// If the user clicked cancel, don't do anything
if (personaName === false) {
if (personaName === null && !personaUnbind) {
return;
}
if (personaName.length > 0) {
if (personaName && personaName.length > 0) {
// If the user clicked ok and entered a name, bind the name to the persona
console.log(`Binding persona ${avatarId} to name ${personaName}`);
power_user.personas[avatarId] = personaName;
@ -643,7 +646,12 @@ async function lockPersona() {
);
}
power_user.personas[user_avatar] = name1;
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT };
power_user.persona_descriptions[user_avatar] = {
description: '',
position: persona_description_positions.IN_PROMPT,
depth: DEFAULT_DEPTH,
role: DEFAULT_ROLE,
};
}
chat_metadata['persona'] = user_avatar;
@ -672,7 +680,7 @@ async function deleteUserAvatar(e) {
return;
}
const confirm = await callPopup('<h3>Are you sure you want to delete this avatar?</h3>All information associated with its linked persona will be lost.', 'confirm');
const confirm = await Popup.show.confirm('Are you sure you want to delete this avatar?', 'All information associated with its linked persona will be lost.');
if (!confirm) {
console.debug('User cancelled deleting avatar');
@ -806,7 +814,7 @@ async function setDefaultPersona(e) {
const personaName = power_user.personas[avatarId];
if (avatarId === currentDefault) {
const confirm = await callPopup('Are you sure you want to remove the default persona?', 'confirm');
const confirm = await Popup.show.confirm('Are you sure you want to remove the default persona?', personaName);
if (!confirm) {
console.debug('User cancelled removing default persona');
@ -819,8 +827,7 @@ async function setDefaultPersona(e) {
}
delete power_user.default_persona;
} else {
const confirm = await callPopup(`<h3>Are you sure you want to set "${personaName}" as the default persona?</h3>
This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.`, 'confirm');
const confirm = await Popup.show.confirm(`Are you sure you want to set "${personaName}" as the default persona?`, 'This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.');
if (!confirm) {
console.debug('User cancelled setting default persona');
@ -978,7 +985,7 @@ async function onPersonasRestoreInput(e) {
}
async function syncUserNameToPersona() {
const confirmation = await callPopup(`<h3>Are you sure?</h3>All user-sent messages in this chat will be attributed to ${name1}.`, 'confirm');
const confirmation = await Popup.show.confirm('Are you sure?', `All user-sent messages in this chat will be attributed to ${name1}.`);
if (!confirmation) {
return;
@ -1001,6 +1008,42 @@ export function retriggerFirstMessageOnEmptyChat() {
}
}
/**
* Duplicates a persona.
* @param {string} avatarId
* @returns {Promise<void>}
*/
async function duplicatePersona(avatarId) {
const personaName = power_user.personas[avatarId];
if (!personaName) {
toastr.warning('Chosen avatar is not a persona');
return;
}
const confirm = await Popup.show.confirm('Are you sure you want to duplicate this persona?', personaName);
if (!confirm) {
console.debug('User cancelled duplicating persona');
return;
}
const newAvatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
const descriptor = power_user.persona_descriptions[avatarId];
power_user.personas[newAvatarId] = personaName;
power_user.persona_descriptions[newAvatarId] = {
description: descriptor?.description ?? '',
position: descriptor?.position ?? persona_description_positions.IN_PROMPT,
depth: descriptor?.depth ?? DEFAULT_DEPTH,
role: descriptor?.role ?? DEFAULT_ROLE,
};
await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId);
await getUserAvatars(true, newAvatarId);
saveSettingsDebounced();
}
export function initPersonas() {
$(document).on('click', '.bind_user_name', bindUserNameToPersona);
$(document).on('click', '.set_default_persona', setDefaultPersona);
@ -1059,6 +1102,18 @@ export function initPersonas() {
$('#avatar_upload_file').trigger('click');
});
$(document).on('click', '#user_avatar_block .duplicate_persona', function (e) {
e.stopPropagation();
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
if (!avatarId) {
console.log('no imgfile');
return;
}
duplicatePersona(avatarId);
});
$(document).on('click', '#user_avatar_block .set_persona_image', function (e) {
e.stopPropagation();
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');

View File

@ -73,8 +73,8 @@ const showPopupHelper = {
/**
* Asynchronously displays an input popup with the given header and text, and returns the user's input.
*
* @param {string} header - The header text for the popup.
* @param {string} text - The main text for the popup.
* @param {string?} header - The header text for the popup.
* @param {string?} text - The main text for the popup.
* @param {string} [defaultValue=''] - The default value for the input field.
* @param {PopupOptions} [popupOptions={}] - Options for the popup.
* @return {Promise<string?>} A Promise that resolves with the user's input.
@ -600,15 +600,15 @@ class PopupUtils {
/**
* Builds popup content with header and text below
*
* @param {string} header - The header to be added to the text
* @param {string} text - The main text content
* @param {string?} header - The header to be added to the text
* @param {string?} text - The main text content
*/
static BuildTextWithHeader(header, text) {
if (!header) {
return text;
}
return `<h3>${header}</h3>
${text}`;
${text ?? ''}`; // Convert no text to empty string
}
}

View File

@ -2180,7 +2180,7 @@ function validateStoryString(storyString, params) {
validateMissingField('personality');
validateMissingField('persona');
validateMissingField('scenario');
validateMissingField('system');
// validateMissingField('system');
validateMissingField('wiBefore', 'loreBefore');
validateMissingField('wiAfter', 'loreAfter');
@ -2973,7 +2973,13 @@ function setAvgBG() {
return '';
}
async function setThemeCallback(_, text) {
async function setThemeCallback(_, themeName) {
if (!themeName) {
// allow reporting of the theme name if called without args
// for use in ST Scripts via pipe
return power_user.theme;
}
// @ts-ignore
const fuse = new Fuse(themes, {
keys: [
@ -2981,12 +2987,12 @@ async function setThemeCallback(_, text) {
],
});
const results = fuse.search(text);
console.debug('Theme fuzzy search results for ' + text, results);
const results = fuse.search(themeName);
console.debug('Theme fuzzy search results for ' + themeName, results);
const theme = results[0]?.item;
if (!theme) {
toastr.warning(`Could not find theme with name: ${text}`);
toastr.warning(`Could not find theme with name: ${themeName}`);
return;
}
@ -3333,10 +3339,11 @@ $(document).ready(() => {
});
$('#chat_width_slider').on('input', function (e) {
$('#chat_width_slider').on('input', function (e, data) {
const applyMode = data?.forced ? 'forced' : 'normal';
power_user.chat_width = Number(e.target.value);
localStorage.setItem(storage_keys.chat_width, power_user.chat_width);
applyChatWidth();
applyChatWidth(applyMode);
setHotswapsDebounced();
});
@ -3362,11 +3369,12 @@ $(document).ready(() => {
saveSettingsDebounced();
});
$('input[name="font_scale"]').on('input', async function (e) {
$('input[name="font_scale"]').on('input', async function (e, data) {
const applyMode = data?.forced ? 'forced' : 'normal';
power_user.font_scale = Number(e.target.value);
$('#font_scale_counter').val(power_user.font_scale);
localStorage.setItem(storage_keys.font_scale, power_user.font_scale);
await applyFontScale();
await applyFontScale(applyMode);
saveSettingsDebounced();
});
@ -4065,13 +4073,30 @@ $(document).ready(() => {
callback: setThemeCallback,
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'name',
description: 'theme name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => themes.map(theme => new SlashCommandEnumValue(theme.name)),
}),
],
helpString: 'sets a UI theme by name',
helpString: `
<div>
Sets a UI theme by name.
</div>
<div>
If no theme name is is provided, this will return the currently active theme.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/theme Cappuccino</code></pre>
</li>
<li>
<pre><code>/theme</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'movingui',

View File

@ -142,9 +142,8 @@ export function initDefaultSlashCommands() {
returns: 'the current background',
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'filename',
description: 'background filename',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => [...document.querySelectorAll('.bg_example')]
.map(it => new SlashCommandEnumValue(it.getAttribute('bgfile')))
.filter(it => it.value?.length),
@ -154,12 +153,18 @@ export function initDefaultSlashCommands() {
<div>
Sets a background according to the provided filename. Partial names allowed.
</div>
<div>
If no background is provided, this will return the currently selected background.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/bg beach.jpg</code></pre>
</li>
<li>
<pre><code>/bg</code></pre>
</li>
</ul>
</div>
`,
@ -1359,7 +1364,7 @@ export function initDefaultSlashCommands() {
enumProvider: commonEnumProviders.injects,
}),
new SlashCommandNamedArgument(
'position', 'injection position', [ARGUMENT_TYPE.STRING], false, false, 'after', ['before', 'after', 'chat'],
'position', 'injection position', [ARGUMENT_TYPE.STRING], false, false, 'after', ['before', 'after', 'chat', 'none'],
),
new SlashCommandNamedArgument(
'depth', 'injection depth', [ARGUMENT_TYPE.NUMBER], false, false, '4',
@ -1387,7 +1392,7 @@ export function initDefaultSlashCommands() {
'text', [ARGUMENT_TYPE.STRING], false,
),
],
helpString: 'Injects a text into the LLM prompt for the current chat. Requires a unique injection ID. Positions: "before" main prompt, "after" main prompt, in-"chat" (default: after). Depth: injection depth for the prompt (default: 4). Role: role for in-chat injections (default: system). Scan: include injection content into World Info scans (default: false).',
helpString: 'Injects a text into the LLM prompt for the current chat. Requires a unique injection ID. Positions: "before" main prompt, "after" main prompt, in-"chat", hidden with "none" (default: after). Depth: injection depth for the prompt (default: 4). Role: role for in-chat injections (default: system). Scan: include injection content into World Info scans (default: false). Hidden injects in "none" position are not inserted into the prompt but can be used for triggering WI entries.',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'listinjects',
@ -1504,6 +1509,7 @@ function injectCallback(args, value) {
'before': extension_prompt_types.BEFORE_PROMPT,
'after': extension_prompt_types.IN_PROMPT,
'chat': extension_prompt_types.IN_CHAT,
'none': extension_prompt_types.NONE,
};
const roles = {
'system': extension_prompt_roles.SYSTEM,
@ -2879,7 +2885,6 @@ export async function sendMessageAs(args, text) {
if (args.name) {
name = args.name.trim();
mesText = text.trim();
if (!name && !text) {
toastr.warning('You must specify a name and text to send as');
@ -2892,8 +2897,14 @@ export async function sendMessageAs(args, text) {
localStorage.setItem(namelessWarningKey, 'true');
}
name = name2;
if (!text) {
toastr.warning('You must specify text to send as');
return '';
}
}
mesText = text.trim();
// Requires a regex check after the slash command is pushed to output
mesText = getRegexedString(mesText, regex_placement.SLASH_COMMAND, { characterOverride: name });

View File

@ -72,6 +72,8 @@
<li><tt>&lcub;&lcub;instructSystemInstructionPrefix&rcub;&rcub;</tt> <span data-i18n="help_macros_56">instruct system instruction prefix</span></li>
<li><tt>&lcub;&lcub;instructUserFiller&rcub;&rcub;</tt> <span data-i18n="help_macros_57">instruct first user message filler</span></li>
<li><tt>&lcub;&lcub;instructStop&rcub;&rcub;</tt> <span data-i18n="help_macros_58">instruct stop sequence</span></li>
<li><tt>&lcub;&lcub;instructFirstUserPrefix&rcub;&rcub;</tt> <span data-i18n="help_macros_first_user">instruct user first input sequence</span></li>
<li><tt>&lcub;&lcub;instructLastUserPrefix&rcub;&rcub;</tt> <span data-i18n="help_macros_last_user">instruct user last input sequence</span></li>
</ul>
<div data-i18n="Chat variables Macros:">
Chat variables Macros:

View File

@ -1,6 +1,6 @@
<div id="tabby_downloader_popup">
<div>
<h3><strong data-i18n="">Download Model</strong>
<h3><strong data-i18n="Download Model">Download Model</strong>
<a href="https://github.com/theroyallab/async-hf-downloader" class="notes-link" target="_blank">
<span class="note-link-span">?</span>
</a>
@ -58,4 +58,4 @@
<textarea class="text_pole textarea_compact" name="tabby_download_exclude" placeholder="Ex. *.txt"></textarea>
</div>
</div>
</div>
</div>

View File

@ -31,7 +31,7 @@
<h3 data-i18n="Confused or lost?">Confused or lost?</h3>
<ul>
<li>
<span class="note-link-span"><a class="fa-solid fa-circle-question" target="_blank" href="https://docs.sillytavern.app/" data-i18n=""></a></span> - <span data-i18n="click these icons!">click these icons!</span>
<span class="note-link-span"><a class="fa-solid fa-circle-question" target="_blank" href="https://docs.sillytavern.app/"></a></span> - <span data-i18n="click these icons!">click these icons!</span>
</li>
<li>
<span data-i18n="Enter">Enter </span><code>/?</code><span data-i18n="in the chat bar"> in the chat bar</span>

View File

@ -1184,7 +1184,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
if (settings.type === HUGGINGFACE) {
params.top_p = Math.min(Math.max(Number(params.top_p), 0.0), 0.999);
params.stop = Array.isArray(params.stop) ? params.stop.slice(0, 4) : [];
nonAphroditeParams.seed = settings.seed >= 0 ? settings.seed : undefined;
nonAphroditeParams.seed = settings.seed >= 0 ? settings.seed : Math.floor(Math.random() * Math.pow(2, 32));
}
if (settings.type === MANCER) {

View File

@ -704,6 +704,22 @@ export function isOdd(number) {
return number % 2 !== 0;
}
/**
* Compare two moment objects for sorting.
* @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) {
if (a.isBefore(b)) {
return 1;
} else if (a.isAfter(b)) {
return -1;
} else {
return 0;
}
}
const dateCache = new Map();
/**
@ -717,26 +733,27 @@ export function timestampToMoment(timestamp) {
return dateCache.get(timestamp);
}
const moment = parseTimestamp(timestamp);
dateCache.set(timestamp, moment);
return moment;
const iso8601 = parseTimestamp(timestamp);
const objMoment = iso8601 ? moment(iso8601) : moment.invalid();
dateCache.set(timestamp, objMoment);
return objMoment;
}
/**
* Parses a timestamp and returns a moment object representing the parsed date and time.
* @param {string|number} timestamp - The timestamp to parse. It can be a string or a number.
* @returns {moment.Moment} - A moment object representing the parsed date and time. If the timestamp is invalid, an invalid moment object is returned.
* @returns {string} - If the timestamp is valid, returns an ISO 8601 string.
*/
function parseTimestamp(timestamp) {
if (!timestamp) return moment.invalid();
if (!timestamp) return;
// Unix time (legacy TAI / tags)
if (typeof timestamp === 'number' || /^\d+$/.test(timestamp)) {
const number = Number(timestamp);
if (isNaN(number)) return moment.invalid();
if (!isFinite(number)) return moment.invalid();
if (number < 0) return moment.invalid();
return moment(number);
const unixTime = Number(timestamp);
const isValid = Number.isFinite(unixTime) && !Number.isNaN(unixTime) && unixTime >= 0;
if (!isValid) return;
return new Date(unixTime).toISOString();
}
let dtFmt = [];
@ -760,32 +777,12 @@ function parseTimestamp(timestamp) {
// 2024-6-5 @14h 56m 50s 682ms
dtFmt.push({ callback: convertFromHumanized, pattern: /(\d{4})-(\d{1,2})-(\d{1,2}) @(\d{1,2})h (\d{1,2})m (\d{1,2})s (\d{1,3})ms/ });
let iso8601;
for (const x of dtFmt) {
let rgxMatch = timestamp.match(x.pattern);
if (!rgxMatch) continue;
iso8601 = x.callback(...rgxMatch);
break;
}
// If one of the patterns matched, return a valid moment object, otherwise return an invalid moment object
return iso8601 ? moment(iso8601) : moment.invalid();
}
/**
* Compare two moment objects for sorting.
* @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) {
if (a.isBefore(b)) {
return 1;
} else if (a.isAfter(b)) {
return -1;
} else {
return 0;
return x.callback(...rgxMatch);
}
return;
}
/** Split string to parts no more than length in size.

View File

@ -893,7 +893,7 @@ function registerWorldInfoSlashCommands() {
* @param {boolean} [loadIfNotSelected=false] - Indicates whether to load the file even if it's not currently selected
*/
function reloadEditor(file, loadIfNotSelected = false) {
const currentIndex = $('#world_editor_select').val();
const currentIndex = Number($('#world_editor_select').val());
const selectedIndex = world_names.indexOf(file);
if (selectedIndex !== -1 && (loadIfNotSelected || currentIndex === selectedIndex)) {
$('#world_editor_select').val(selectedIndex).trigger('change');
@ -1647,32 +1647,38 @@ function sortEntries(data) {
if (!data.length) return data;
/** @type {(a: any, b: any) => number} */
let primarySort;
// Secondary and tertiary it will always be sorted by Order descending, and last UID ascending
// This is the most sensible approach for sorts where the primary sort has a lot of equal values
const secondarySort = (a, b) => b.order - a.order;
const tertiarySort = (a, b) => a.uid - b.uid;
// If we have a search term for WI, we are sorting by weighting scores
if (sortRule === 'search') {
data.sort((a, b) => {
primarySort = (a, b) => {
const aScore = worldInfoFilter.getScore(FILTER_TYPES.WORLD_INFO_SEARCH, a.uid);
const bScore = worldInfoFilter.getScore(FILTER_TYPES.WORLD_INFO_SEARCH, b.uid);
return (aScore - bScore);
});
return aScore - bScore;
};
}
else if (sortRule === 'custom') {
// First by display index, then by order, then by uid
data.sort((a, b) => {
// First by display index
primarySort = (a, b) => {
const aValue = a.displayIndex;
const bValue = b.displayIndex;
return (aValue - bValue || b.order - a.order || a.uid - b.uid);
});
return aValue - bValue;
};
} else if (sortRule === 'priority') {
// First constant, then normal, then disabled. Then sort by order
data.sort((a, b) => {
// First constant, then normal, then disabled.
primarySort = (a, b) => {
const aValue = a.constant ? 0 : a.disable ? 2 : 1;
const bValue = b.constant ? 0 : b.disable ? 2 : 1;
return (aValue - bValue || b.order - a.order);
});
return aValue - bValue;
};
} else {
const primarySort = (a, b) => {
primarySort = (a, b) => {
const aValue = a[sortField];
const bValue = b[sortField];
@ -1690,26 +1696,12 @@ function sortEntries(data) {
// Sort numbers
return orderSign * (Number(aValue) - Number(bValue));
};
const secondarySort = (a, b) => a.order - b.order;
const tertiarySort = (a, b) => a.uid - b.uid;
data.sort((a, b) => {
const primary = primarySort(a, b);
if (primary !== 0) {
return primary;
}
const secondary = secondarySort(a, b);
if (secondary !== 0) {
return secondary;
}
return tertiarySort(a, b);
});
}
data.sort((a, b) => {
return primarySort(a, b) || secondarySort(a, b) || tertiarySort(a, b);
});
return data;
}
@ -1939,39 +1931,43 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
}
});
$('#world_apply_custom_sorting').off('click').on('click', async () => {
$('#world_apply_current_sorting').off('click').on('click', async () => {
const entryCount = Object.keys(data.entries).length;
const moreThan100 = entryCount > 100;
let content = '<span>Apply your custom sorting to the "Order" field. The Order values will go down from the chosen number.</span>';
let content = '<span>Apply your current sorting to the "Order" field. The Order values will go down from the chosen number.</span>';
if (moreThan100) {
content += `<div class="m-t-1"><i class="fa-solid fa-triangle-exclamation" style="color: #FFD43B;"></i> More than 100 entries in this world. If you don't choose a number higher than that, the lower entries will default to 0.<br />(Usual default: 100)<br />Minimum: ${entryCount}</div>`;
}
const result = await Popup.show.input('Apply Custom Sorting', content, '100', { okButton: 'Apply', cancelButton: 'Cancel' });
const result = await Popup.show.input('Apply Current Sorting', content, '100', { okButton: 'Apply', cancelButton: 'Cancel' });
if (!result) return;
const start = Number(result);
if (isNaN(start) || start < 0) {
toastr.error('Invalid number: ' + result, 'Apply Custom Sorting');
toastr.error('Invalid number: ' + result, 'Apply Current Sorting');
return;
}
if (start < entryCount) {
toastr.warning('A number lower than the entry count has been chosen. All entries below that will default to 0.', 'Apply Custom Sorting');
toastr.warning('A number lower than the entry count has been chosen. All entries below that will default to 0.', 'Apply Current Sorting');
}
let counter = 0;
for (const entry of Object.values(data.entries)) {
const newOrder = Math.max(start - (entry.displayIndex ?? 0), 0);
// We need to sort the entries here, as the data source isn't sorted
const entries = Object.values(data.entries);
sortEntries(entries);
let updated = 0, current = start;
for (const entry of entries) {
const newOrder = Math.max(current--, 0);
if (entry.order === newOrder) continue;
entry.order = newOrder;
setOriginalDataValue(data, entry.order, 'order', entry.order);
counter++;
updated++;
}
if (counter > 0) {
toastr.info(`Updated ${counter} Order values`, 'Apply Custom Sorting');
if (updated > 0) {
toastr.info(`Updated ${updated} Order values`, 'Apply Custom Sorting');
await saveWorldInfo(name, data, true);
updateEditor(navigation_option.previous);
} else {

View File

@ -137,7 +137,6 @@ body {
width: 100%;
/*fallback for JS load*/
height: 100vh;
height: 100svh;
height: 100dvh;
/*defaults as 100%, then reassigned via JS as pixels, will work on PC and Android*/
/*height: calc(var(--doc-height) - 1px);*/
@ -451,7 +450,7 @@ code {
border-radius: 5px;
background-color: var(--black70a);
padding: 0 3px;
/* max-width: calc(100svw - 95px); */
/* max-width: calc(100dvw - 95px); */
line-height: var(--mainFontSize);
color: var(--white70a);
}
@ -535,13 +534,13 @@ body.reduced-motion #bg_custom {
flex-direction: column;
/* -1px to give sheld some wiggle room to bounce off tobar when moving*/
height: calc(100vh - var(--topBarBlockSize) - 1px);
height: calc(100svh - var(--topBarBlockSize) - 1px);
max-height: calc(100svh - var(--topBarBlockSize) - 1px);
height: calc(100dvh - var(--topBarBlockSize) - 1px);
max-height: calc(100dvh - var(--topBarBlockSize) - 1px);
overflow-x: hidden;
/* max-width: 50vw; */
position: absolute;
left: calc((100vw - var(--sheldWidth))/2);
left: calc((100svw - var(--sheldWidth))/2);
left: calc((100dvw - var(--sheldWidth))/2);
top: var(--topBarBlockSize);
margin: 0 auto;
left: 0;
@ -1160,12 +1159,12 @@ textarea {
font-family: var(--mainFontFamily);
padding: 5px 10px;
max-height: 90vh;
max-height: 90svh;
max-height: 90dvh;
}
textarea.autoSetHeight {
max-height: 50vh;
max-height: 50svh;
max-height: 50dvh;
}
input,
@ -1179,7 +1178,7 @@ select {
min-height: calc(var(--bottomFormBlockSize) + 2px);
height: calc(var(--bottomFormBlockSize) + 2px);
max-height: 50vh;
max-height: 50svh;
max-height: 50dvh;
word-wrap: break-word;
resize: vertical;
display: block;
@ -2159,14 +2158,14 @@ textarea::placeholder {
@media screen and (max-width: 1000px) {
#form_create textarea {
flex-grow: 1;
min-height: 20svh;
min-height: 20dvh;
}
}
@media screen and (min-width: 1001px) {
#description_textarea {
height: 29vh;
height: 29svh;
height: 29dvh;
}
#firstmessage_textarea {
@ -2442,8 +2441,8 @@ input[type="file"] {
#floatingPrompt,
#cfgConfig {
overflow-y: auto;
max-width: 90svw;
max-height: 90svh;
max-width: 90dvw;
max-height: 90dvh;
min-width: 100px;
min-height: 100px;
border-radius: 10px;
@ -2459,7 +2458,7 @@ input[type="file"] {
top: 0;
margin: 0;
right: unset;
width: calc(((100svw - var(--sheldWidth)) / 2) - 1px);
width: calc(((100dvw - var(--sheldWidth)) / 2) - 1px);
}
@ -2816,7 +2815,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
flex-wrap: wrap;
width: calc(var(--sheldWidth) - 10px);
max-width: 100vw;
max-width: 100svw;
max-width: 100dvw;
justify-content: space-evenly;
}
@ -3080,6 +3079,10 @@ grammarly-extension {
opacity: 1;
}
.avatar-container .avatar-buttons .menu_button {
padding: 3px;
}
/* Ross should be able to handle this later */
/*.big-avatars .avatar-buttons{
justify-content: center;
@ -3171,7 +3174,7 @@ grammarly-extension {
#dialogue_popup {
width: 500px;
max-width: 90vw;
max-width: 90svw;
max-width: 90dvw;
position: absolute;
z-index: 9999;
margin-left: auto;
@ -3187,7 +3190,7 @@ grammarly-extension {
background-color: var(--SmartThemeBlurTintColor);
border-radius: 10px;
max-height: 90vh;
max-height: 90svh;
max-height: 90dvh;
display: flex;
flex-direction: column;
overflow-y: hidden;
@ -3201,9 +3204,9 @@ grammarly-extension {
.large_dialogue_popup {
height: 90vh !important;
height: 90svh !important;
height: 90dvh !important;
max-width: 90vw !important;
max-width: 90svw !important;
max-width: 90dvw !important;
}
.wide_dialogue_popup {
@ -3360,7 +3363,7 @@ grammarly-extension {
position: absolute;
width: 100%;
height: 100vh;
height: 100svh;
height: 100dvh;
z-index: 9999;
top: 0;
}
@ -3368,9 +3371,9 @@ grammarly-extension {
#bgtest {
display: none;
width: 100vw;
width: 100svw;
width: 100dvw;
height: 100vh;
height: 100svh;
height: 100dvh;
position: absolute;
z-index: -100;
background-color: red;
@ -4007,7 +4010,7 @@ input[type="range"]::-webkit-slider-thumb {
position: absolute;
width: 100%;
height: 100vh;
height: 100svh;
height: 100dvh;
z-index: 2058;
}
@ -4020,11 +4023,11 @@ input[type="range"]::-webkit-slider-thumb {
min-width: 100px;
max-width: var(--sheldWidth);
height: calc(100vh - 84px);
height: calc(100svh - 84px);
height: calc(100dvh - 84px);
min-height: calc(100vh - 84px);
min-height: calc(100svh - 84px);
min-height: calc(100dvh - 84px);
max-height: calc(100vh - 84px);
max-height: calc(100svh - 84px);
max-height: calc(100dvh - 84px);
position: absolute;
z-index: 4001;
margin-left: auto;
@ -4103,7 +4106,7 @@ h5 {
position: absolute;
width: 100%;
height: 100vh;
height: 100svh;
height: 100dvh;
z-index: 4100;
top: 0;
background-color: var(--black70a);
@ -4117,7 +4120,7 @@ h5 {
max-width: var(--sheldWidth);
height: min-content;
max-height: calc(100vh - var(--topBarBlockSize));
max-height: calc(100svh - var(--topBarBlockSize));
max-height: calc(100dvh - var(--topBarBlockSize));
min-height: 100px;
position: absolute;
z-index: 2066;
@ -4437,14 +4440,14 @@ a {
overflow-wrap: break-word;
white-space: normal;
max-width: calc(((100vw - 500px) / 2) - 10px);
max-width: calc(((100svw - 500px) / 2) - 10px);
max-width: calc(((100dvw - 500px) / 2) - 10px);
position: absolute;
z-index: 9999;
max-height: 90vh;
max-height: 90svh;
max-height: 90dvh;
/*unsure why, but this prevents scrollbars*/
height: 49vh;
height: 49svh;
height: 49dvh;
padding: 5px;
overflow-y: auto;
@ -4480,11 +4483,11 @@ a {
#right-nav-panel {
width: calc((100vw - var(--sheldWidth) - 2px) /2);
width: calc((100svw - var(--sheldWidth) - 2px) /2);
width: calc((100dvw - var(--sheldWidth) - 2px) /2);
max-height: calc(100vh - var(--topBarBlockSize));
max-height: calc(100svh - var(--topBarBlockSize));
max-height: calc(100dvh - var(--topBarBlockSize));
height: calc(100vh - var(--topBarBlockSize));
height: calc(100svh - var(--topBarBlockSize));
height: calc(100dvh - var(--topBarBlockSize));
position: fixed;
top: 0;
margin: 0;
@ -4537,7 +4540,7 @@ a {
border-radius: 10px;
max-width: 100%;
max-height: 40vh;
max-height: 40svh;
max-height: 40dvh;
image-rendering: -webkit-optimize-contrast;
}
@ -4629,18 +4632,18 @@ body:not(.caption) .mes_img_caption {
.img_enlarged_container pre {
max-height: 25vh;
max-height: 25svh;
max-height: 25dvh;
flex-shrink: 0;
overflow: auto;
}
.popup:has(.img_enlarged.zoomed).large_dialogue_popup {
height: 100vh !important;
height: 100svh !important;
height: 100dvh !important;
max-height: 100vh !important;
max-height: 100svh !important;
max-height: 100dvh !important;
max-width: 100vw !important;
max-width: 100svw !important;
max-width: 100dvw !important;
padding: 0;
}
@ -4827,7 +4830,7 @@ body:has(#character_popup.open) #top-settings-holder:has(.drawer-content.openDra
width: var(--sheldWidth);
overflow-y: auto;
max-height: calc(100vh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
max-height: calc(100svh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
max-height: calc(100dvh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
display: none;
position: absolute;
top: var(--topBarBlockSize);
@ -4862,11 +4865,11 @@ body:not(.movingUI) .drawer-content.maximized {
.fillLeft {
width: calc((100vw - var(--sheldWidth) - 2px) /2);
width: calc((100svw - var(--sheldWidth) - 2px) /2);
width: calc((100dvw - var(--sheldWidth) - 2px) /2);
height: calc(100vh - var(--topBarBlockSize));
height: calc(100svh - var(--topBarBlockSize));
height: calc(100dvh - var(--topBarBlockSize));
max-height: calc(100vh - var(--topBarBlockSize));
max-height: calc(100svh - var(--topBarBlockSize));
max-height: calc(100dvh - var(--topBarBlockSize));
position: fixed;
top: 0;
margin: 0;
@ -5116,7 +5119,7 @@ body:not(.movingUI) .drawer-content.maximized {
width: 100%;
/* margin-inline: 10px; */
max-height: 90vh;
max-width: 90svh;
max-width: 90dvh;
}
.zoomed_avatar img {

View File

@ -1,4 +1,5 @@
require('./polyfill.js');
const { getConfigValue } = require('./util.js');
/**
* Convert a prompt from the ChatML objects to the format used by Claude.
@ -373,8 +374,9 @@ function convertMistralMessages(messages, charName = '', userName = '') {
}
// Make the last assistant message a prefill
const prefixEnabled = getConfigValue('mistral.enablePrefix', false);
const lastMsg = messages[messages.length - 1];
if (messages.length > 0 && lastMsg && (lastMsg.role === 'assistant')) {
if (prefixEnabled && messages.length > 0 && lastMsg?.role === 'assistant') {
lastMsg.prefix = true;
}