Merge branch 'staging' into parser-followup-2

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

View File

@@ -93,6 +93,11 @@ openai:
deepl: deepl:
# Available options: default, more, less, prefer_more, prefer_less # Available options: default, more, less, prefer_more, prefer_less
formality: default 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 -- # -- SERVER PLUGIN CONFIGURATION --
enableServerPlugins: false enableServerPlugins: false
# User session timeout *in seconds* (defaults to 24 hours). # User session timeout *in seconds* (defaults to 24 hours).

4
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,8 +16,8 @@ dialog {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: calc(100svh - 2em); max-height: calc(100dvh - 2em);
max-width: calc(100svw - 2em); max-width: calc(100dvw - 2em);
min-height: fit-content; 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. */ /* 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 { .popup #toast-container {
/* Fix toastr in dialogs by actually placing it at the top of the screen via transform */ /* 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)); top: calc(50% + var(--topBarBlockSize));
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
@@ -115,7 +115,7 @@ body.no-blur .popup[open]::backdrop {
.popup-crop-wrap { .popup-crop-wrap {
margin: 10px auto; margin: 10px auto;
max-height: 75vh; max-height: 75vh;
max-height: 75svh; max-height: 75dvh;
max-width: 100%; max-width: 100%;
} }

View File

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

2
public/global.d.ts vendored
View File

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

View File

@@ -3296,6 +3296,24 @@
</div> </div>
</div> </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="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."> <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"> <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="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="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_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" <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>
title="Apply custom sorting as Order" data-i18n="[title]Apply custom sorting as Order"></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_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_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> <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"> <input type="range" id="smooth_streaming_speed" name="smooth_streaming_speed" min="0" max="100" step="10" value="50">
<div class="slider_hint"> <div class="slider_hint">
<span data-i18n="Slow">Slow</span> <span data-i18n="Slow">Slow</span>
<span data-i18n=""></span> <span></span>
<span data-i18n="Fast">Fast</span> <span data-i18n="Fast">Fast</span>
</div> </div>
</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."> <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> <i class="fa-fw fa-solid fa-crown"></i>
</button> </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"> <button class="menu_button delete_avatar" title="Delete persona" data-i18n="[title]Delete persona">
<i class="fa-fw fa-solid fa-trash-alt"></i> <i class="fa-fw fa-solid fa-trash-alt"></i>
</button> </button>

View File

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

View File

@@ -570,6 +570,7 @@ export const system_message_types = {
* @enum {number} Extension prompt types * @enum {number} Extension prompt types
*/ */
export const extension_prompt_types = { export const extension_prompt_types = {
NONE: -1,
IN_PROMPT: 0, IN_PROMPT: 0,
IN_CHAT: 1, IN_CHAT: 1,
BEFORE_PROMPT: 2, BEFORE_PROMPT: 2,
@@ -929,10 +930,17 @@ async function firstLoadInit() {
initCfg(); initCfg();
initLogprobs(); initLogprobs();
doDailyExtensionUpdatesCheck(); doDailyExtensionUpdatesCheck();
hideLoader(); await hideLoader();
await fixViewport();
await eventSource.emit(event_types.APP_READY); await eventSource.emit(event_types.APP_READY);
} }
async function fixViewport() {
document.body.style.position = 'absolute';
await delay(1);
document.body.style.position = '';
}
function cancelStatusCheck() { function cancelStatusCheck() {
abortStatusCheck?.abort(); abortStatusCheck?.abort();
abortStatusCheck = new AbortController(); abortStatusCheck = new AbortController();
@@ -3611,6 +3619,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
let chat2 = []; let chat2 = [];
let continue_mag = ''; let continue_mag = '';
const userMessageIndices = []; const userMessageIndices = [];
const lastUserMessageIndex = coreChat.findLastIndex(x => x.is_user);
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) { for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
if (main_api == 'openai') { 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); 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 // Do not suffix the message for continuation
if (i === 0 && isContinue) { if (i === 0 && isContinue) {
if (isInstruct) { if (isInstruct) {
@@ -3654,7 +3668,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
mes: power_user.instruct.user_alignment_message, mes: power_user.instruct.user_alignment_message,
is_user: true, is_user: true,
}; };
userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, false); userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, force_output_sequence.FIRST);
} }
// Call combined AN into Generate // Call combined AN into Generate
@@ -6053,9 +6067,10 @@ async function getChatResult() {
const message = getFirstMessage(); const message = getFirstMessage();
if (message.mes) { if (message.mes) {
chat.push(message); chat.push(message);
await saveChatConditional();
freshChat = true; freshChat = true;
} }
// Make sure the chat appears on the server
await saveChatConditional();
} }
await loadItemizedPrompts(getCurrentChatId()); await loadItemizedPrompts(getCurrentChatId());
await printMessages(); await printMessages();
@@ -6107,7 +6122,7 @@ export async function openCharacterChat(file_name) {
chat_metadata = {}; chat_metadata = {};
await getChat(); await getChat();
$('#selected_chat_pole').val(file_name); $('#selected_chat_pole').val(file_name);
await createOrEditCharacter(); await createOrEditCharacter(new CustomEvent('newChat'));
} }
////////// OPTIMZED MAIN API CHANGE FUNCTION //////////// ////////// OPTIMZED MAIN API CHANGE FUNCTION ////////////
@@ -6809,6 +6824,11 @@ export async function displayPastChats() {
const fileName = chat['file_name']; const fileName = chat['file_name'];
const chatContent = rawChats[fileName]; 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). // // Uncomment this to return to old behavior (classical full-substring search).
// return chatContent && Object.values(chatContent).some(message => message?.mes?.toLowerCase()?.includes(searchQuery.toLowerCase())); // 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', button: '#api_button_textgenerationwebui',
type: textgen_types.OPENROUTER, type: textgen_types.OPENROUTER,
}, },
'featherless': {
selected: 'textgenerationwebui',
button: '#api_button_textgenerationwebui',
type: textgen_types.FEATHERLESS,
},
'huggingface': { 'huggingface': {
selected: 'textgenerationwebui', selected: 'textgenerationwebui',
button: '#api_button_textgenerationwebui', button: '#api_button_textgenerationwebui',
@@ -9837,8 +9862,8 @@ jQuery(async function () {
hideMenu(); hideMenu();
}); });
$('#newChatFromManageScreenButton').on('click', function () { $('#newChatFromManageScreenButton').on('click', async function () {
doNewChat({ deleteCurrentChat: false }); await doNewChat({ deleteCurrentChat: false });
$('#select_chat_cross').trigger('click'); $('#select_chat_cross').trigger('click');
}); });
@@ -10810,7 +10835,7 @@ jQuery(async function () {
//newSlider.val(manualInput) //newSlider.val(manualInput)
//handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual'); //handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual');
valueBeforeManualInput = manualInput; valueBeforeManualInput = manualInput;
$(masterElement).val($(this).val()).trigger('input'); $(masterElement).val($(this).val()).trigger('input', { forced: true });
} else { } else {
//if value not ok, warn and reset to last known valid value //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')}`); 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'))) { if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) {
valueBeforeManualInput = manualInput; valueBeforeManualInput = manualInput;
//set the slider value to input value //set the slider value to input value
$(masterElement).val($(this).val()).trigger('input'); $(masterElement).val($(this).val()).trigger('input', { forced: true });
} else { } else {
//if value not ok, warn and reset to last known valid value //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')}`); 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 // 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(); const result = await resolveExpressionsList();
@@ -1618,11 +1619,13 @@ async function onClickExpressionRemoveCustom() {
moduleWorker(); moduleWorker();
} }
function onExperesionApiChanged() { function onExpressionApiChanged() {
const tempApi = this.value; const tempApi = this.value;
if (tempApi) { if (tempApi) {
extension_settings.expressions.api = Number(tempApi); extension_settings.expressions.api = Number(tempApi);
$('.expression_llm_prompt_block').toggle(extension_settings.expressions.api === EXPRESSION_API.llm); $('.expression_llm_prompt_block').toggle(extension_settings.expressions.api === EXPRESSION_API.llm);
expressionsList = null;
spriteCache = {};
moduleWorker(); moduleWorker();
saveSettingsDebounced(); saveSettingsDebounced();
} }
@@ -1972,7 +1975,7 @@ function migrateSettings() {
$('#expression_custom_add').on('click', onClickExpressionAddCustom); $('#expression_custom_add').on('click', onClickExpressionAddCustom);
$('#expression_custom_remove').on('click', onClickExpressionRemoveCustom); $('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
$('#expression_fallback').on('change', onExpressionFallbackChanged); $('#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. // 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, this_chid,
characters, characters,
getRequestHeaders, getRequestHeaders,
event_types,
} from '../../../script.js'; } from '../../../script.js';
import { groups, selected_group } from '../../group-chats.js'; import { groups, selected_group } from '../../group-chats.js';
import { loadFileToDocument, delay } from '../../utils.js'; import { loadFileToDocument, delay } from '../../utils.js';
@@ -25,6 +26,27 @@ let paginationVisiblePages = 10;
let paginationMaxLinesPerPage = 2; let paginationMaxLinesPerPage = 2;
let galleryMaxRows = 3; 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 * 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. * @returns {Promise<void>} - Promise representing the completion of the gallery initialization.
*/ */
async function initGallery(items, url) { async function initGallery(items, url) {
const nonce = `nonce-${Math.random().toString(36).substring(2, 15)}`;
const gallery = $('#dragGallery'); const gallery = $('#dragGallery');
gallery.addClass(nonce);
gallery.nanogallery2({ gallery.nanogallery2({
'items': items, 'items': items,
thumbnailWidth: 'auto', thumbnailWidth: 'auto',
@@ -82,16 +106,26 @@ async function initGallery(items, url) {
fnThumbnailOpen: viewWithDragbox, fnThumbnailOpen: viewWithDragbox,
}); });
const dragDropHandler = new DragAndDropHandler(`#dragGallery.${nonce}`, async (files, event) => {
eventSource.on('resizeUI', function (elmntName) {
gallery.nanogallery2('resize');
});
const dragDropHandler = new DragAndDropHandler('#dragGallery', async (files, event) => {
let file = files[0]; let file = files[0];
uploadFile(file, url); // Added url parameter to know where to upload 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 // Set dropzone height to be the same as the parent
gallery.css('height', gallery.parent().css('height')); gallery.css('height', gallery.parent().css('height'));
@@ -140,16 +174,10 @@ async function showCharGallery() {
const items = await getGalleryItems(url); const items = await getGalleryItems(url);
// if there already is a gallery, destroy it and place this one in its place // if there already is a gallery, destroy it and place this one in its place
if ($('#dragGallery').length) { $('#dragGallery').closest('#gallery').remove();
$('#dragGallery').nanogallery2('destroy'); makeMovable();
initGallery(items, url); await delay(100);
} else { await initGallery(items, url);
makeMovable();
setTimeout(async () => {
await initGallery(items, url);
}, 100);
}
} catch (err) { } catch (err) {
console.trace(); console.trace();
console.error(err); console.error(err);
@@ -202,11 +230,11 @@ async function uploadFile(file, url) {
toastr.success('File uploaded successfully. Saved at: ' + result.path); toastr.success('File uploaded successfully. Saved at: ' + result.path);
// Refresh the gallery // Refresh the gallery
$('#dragGallery').nanogallery2('destroy'); // Destroy old gallery
const newItems = await getGalleryItems(url); // Fetch the latest items 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) { } catch (error) {
console.error('There was an issue uploading the file:', error); console.error('There was an issue uploading the file:', error);
@@ -273,11 +301,6 @@ function makeMovable(id = 'gallery') {
e.preventDefault(); e.preventDefault();
return false; 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 { } else {
console.error('Failed to append the template content or retrieve the appended content.'); 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. // 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'], aliases: ['sg'],
callback: () => { callback: () => {
showCharGallery(); showCharGallery();
@@ -409,7 +428,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery
}, },
helpString: 'Shows the gallery.', helpString: 'Shows the gallery.',
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery', SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'list-gallery',
aliases: ['lg'], aliases: ['lg'],
callback: listGalleryCommand, callback: listGalleryCommand,
returns: 'list of images', returns: 'list of images',
@@ -432,14 +452,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery
async function listGalleryCommand(args) { async function listGalleryCommand(args) {
try { 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) { if (!args.char && !args.group && !selected_group && this_chid) {
const char = characters[this_chid]; const char = characters[this_chid];
url = char.avatar.replace('.png', ''); url = char.avatar.replace('.png', '');
} }
const items = await getGalleryItems(url); const items = await getGalleryItems(url);
return JSON.stringify(items.map(it=>it.src)); return JSON.stringify(items.map(it => it.src));
} catch (err) { } catch (err) {
console.trace(); console.trace();

View File

@@ -5,7 +5,7 @@
"optional": [ "optional": [
], ],
"js": "index.js", "js": "index.js",
"css": "", "css": "style.css",
"author": "City-Unit", "author": "City-Unit",
"version": "1.5.0", "version": "1.5.0",
"homePage": "https://github.com/SillyTavern/SillyTavern" "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, substituteParamsExtended,
generateRaw, generateRaw,
getMaxContextSize, getMaxContextSize,
setExtensionPrompt,
} from '../../../script.js'; } from '../../../script.js';
import { is_group_generating, selected_group } from '../../group-chats.js'; import { is_group_generating, selected_group } from '../../group-chats.js';
import { loadMovingUIState } from '../../power-user.js'; import { loadMovingUIState } from '../../power-user.js';
@@ -73,6 +74,7 @@ const defaultSettings = {
template: defaultTemplate, template: defaultTemplate,
position: extension_prompt_types.IN_PROMPT, position: extension_prompt_types.IN_PROMPT,
role: extension_prompt_roles.SYSTEM, role: extension_prompt_roles.SYSTEM,
scan: false,
depth: 2, depth: 2,
promptWords: 200, promptWords: 200,
promptMinWords: 25, promptMinWords: 25,
@@ -122,6 +124,7 @@ function loadSettings() {
$(`input[name="memory_prompt_builder"][value="${extension_settings.memory.prompt_builder}"]`).prop('checked', true).trigger('input'); $(`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_override_response_length').val(extension_settings.memory.overrideResponseLength).trigger('input');
$('#memory_max_messages_per_request').val(extension_settings.memory.maxMessagesPerRequest).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); switchSourceControls(extension_settings.memory.source);
} }
@@ -279,6 +282,13 @@ function onMemoryPositionChange(e) {
saveSettingsDebounced(); saveSettingsDebounced();
} }
function onMemoryIncludeWIScanInput() {
const value = !!$(this).prop('checked');
extension_settings.memory.scan = value;
reinsertMemory();
saveSettingsDebounced();
}
function onMemoryPromptWordsForceInput() { function onMemoryPromptWordsForceInput() {
const value = $(this).val(); const value = $(this).val();
extension_settings.memory.promptForceWords = Number(value); 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. * @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) { function setMemoryContext(value, saveToMessage, index = null) {
const context = getContext(); setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, extension_settings.memory.scan, extension_settings.memory.role);
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, false, extension_settings.memory.role);
$('#memory_contents').val(value); $('#memory_contents').val(value);
const summaryLog = value const summaryLog = value
@@ -809,6 +818,7 @@ function setMemoryContext(value, saveToMessage, index = null) {
: 'Summary has no content'; : 'Summary has no content';
console.debug(summaryLog); console.debug(summaryLog);
const context = getContext();
if (saveToMessage && context.chat.length) { if (saveToMessage && context.chat.length) {
const idx = index ?? context.chat.length - 2; const idx = index ?? context.chat.length - 2;
const mes = context.chat[idx < 0 ? 0 : idx]; const mes = context.chat[idx < 0 ? 0 : idx];
@@ -894,6 +904,7 @@ function setupListeners() {
$('#memory_prompt_words_auto').off('click').on('click', onPromptForceWordsAutoClick); $('#memory_prompt_words_auto').off('click').on('click', onPromptForceWordsAutoClick);
$('#memory_override_response_length').off('click').on('input', onOverrideResponseLengthInput); $('#memory_override_response_length').off('click').on('input', onOverrideResponseLengthInput);
$('#memory_max_messages_per_request').off('click').on('input', onMaxMessagesPerRequestInput); $('#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 () { $('#summarySettingsBlockToggle').off('click').on('click', function () {
console.log('saw settings button click'); console.log('saw settings button click');
$('#summarySettingsBlock').slideToggle(200, 'swing'); //toggleClass("hidden"); $('#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> <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> </div>
<label for="memory_position" data-i18n="ext_sum_injection_position">Injection Position</label> <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"> <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> <label>
<input type="radio" name="memory_position" value="2" /> <input type="radio" name="memory_position" value="2" />
<span data-i18n="Before Main Prompt / Story String">Before Main Prompt / Story String</span> <span data-i18n="Before Main Prompt / Story String">Before Main Prompt / Story String</span>

View File

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

View File

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

View File

@@ -34,6 +34,8 @@ const controls = [
{ id: 'instruct_names_force_groups', property: 'names_force_groups', isCheckbox: true }, { id: 'instruct_names_force_groups', property: 'names_force_groups', isCheckbox: true },
{ id: 'instruct_first_output_sequence', property: 'first_output_sequence', isCheckbox: false }, { id: 'instruct_first_output_sequence', property: 'first_output_sequence', isCheckbox: false },
{ id: 'instruct_last_output_sequence', property: 'last_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_activation_regex', property: 'activation_regex', isCheckbox: false },
{ id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true }, { id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },
{ id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true }, { id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },
@@ -58,6 +60,8 @@ function migrateInstructModeSettings(settings) {
system_suffix: '', system_suffix: '',
user_alignment_message: '', user_alignment_message: '',
last_system_sequence: '', last_system_sequence: '',
first_input_sequence: '',
last_input_sequence: '',
names_force_groups: true, names_force_groups: true,
skip_examples: false, skip_examples: false,
system_same_as_user: 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 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 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); 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 (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; return power_user.instruct.input_sequence;
} }
@@ -552,6 +572,8 @@ export function replaceInstructMacros(input, env) {
'instructStop': power_user.instruct.stop_sequence, 'instructStop': power_user.instruct.stop_sequence,
'instructUserFiller': power_user.instruct.user_alignment_message, 'instructUserFiller': power_user.instruct.user_alignment_message,
'instructSystemInstructionPrefix': power_user.instruct.last_system_sequence, '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)) { for (const [placeholder, value] of Object.entries(instructMacros)) {

View File

@@ -1,5 +1,4 @@
import { import {
callPopup,
characters, characters,
chat, chat,
chat_metadata, chat_metadata,
@@ -22,7 +21,7 @@ import { PAGINATION_TEMPLATE, debounce, delay, download, ensureImageFormatSuppor
import { debounce_timeout } from './constants.js'; import { debounce_timeout } from './constants.js';
import { FILTER_TYPES, FilterHelper } from './filters.js'; import { FILTER_TYPES, FilterHelper } from './filters.js';
import { selected_group } from './group-chats.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; let savePersonasPage = 0;
const GRID_STORAGE_KEY = 'Personas_GridView'; const GRID_STORAGE_KEY = 'Personas_GridView';
@@ -332,15 +331,14 @@ async function changeUserAvatar(e) {
* @returns {Promise} Promise that resolves when the persona is set * @returns {Promise} Promise that resolves when the persona is set
*/ */
export async function createPersona(avatarId) { 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) { if (!personaName) {
console.debug('User cancelled creating a persona'); console.debug('User cancelled creating a persona');
return; return;
} }
await delay(500); const personaDescription = await Popup.show.input('Enter a description for this persona:', 'You can always add or change it later.', '', { rows: 4 });
const personaDescription = await callPopup('<h3>Enter a description for this persona:</h3>You can always add or change it later.', 'input', '', { rows: 4 });
initPersona(avatarId, personaName, personaDescription); initPersona(avatarId, personaName, personaDescription);
if (power_user.persona_show_notifications) { if (power_user.persona_show_notifications) {
@@ -349,7 +347,7 @@ export async function createPersona(avatarId) {
} }
async function createDummyPersona() { 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) { if (!personaName) {
console.debug('User cancelled creating dummy persona'); console.debug('User cancelled creating dummy persona');
@@ -508,15 +506,20 @@ async function bindUserNameToPersona(e) {
return; return;
} }
let personaUnbind = false;
const existingPersona = power_user.personas[avatarId]; 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 the user clicked cancel, don't do anything
if (personaName === false) { if (personaName === null && !personaUnbind) {
return; 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 // If the user clicked ok and entered a name, bind the name to the persona
console.log(`Binding persona ${avatarId} to name ${personaName}`); console.log(`Binding persona ${avatarId} to name ${personaName}`);
power_user.personas[avatarId] = personaName; power_user.personas[avatarId] = personaName;
@@ -643,7 +646,12 @@ async function lockPersona() {
); );
} }
power_user.personas[user_avatar] = name1; 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; chat_metadata['persona'] = user_avatar;
@@ -672,7 +680,7 @@ async function deleteUserAvatar(e) {
return; 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) { if (!confirm) {
console.debug('User cancelled deleting avatar'); console.debug('User cancelled deleting avatar');
@@ -806,7 +814,7 @@ async function setDefaultPersona(e) {
const personaName = power_user.personas[avatarId]; const personaName = power_user.personas[avatarId];
if (avatarId === currentDefault) { 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) { if (!confirm) {
console.debug('User cancelled removing default persona'); console.debug('User cancelled removing default persona');
@@ -819,8 +827,7 @@ async function setDefaultPersona(e) {
} }
delete power_user.default_persona; delete power_user.default_persona;
} else { } else {
const confirm = await callPopup(`<h3>Are you sure you want to set "${personaName}" as the default persona?</h3> 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.');
This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.`, 'confirm');
if (!confirm) { if (!confirm) {
console.debug('User cancelled setting default persona'); console.debug('User cancelled setting default persona');
@@ -978,7 +985,7 @@ async function onPersonasRestoreInput(e) {
} }
async function syncUserNameToPersona() { 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) { if (!confirmation) {
return; 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() { export function initPersonas() {
$(document).on('click', '.bind_user_name', bindUserNameToPersona); $(document).on('click', '.bind_user_name', bindUserNameToPersona);
$(document).on('click', '.set_default_persona', setDefaultPersona); $(document).on('click', '.set_default_persona', setDefaultPersona);
@@ -1059,6 +1102,18 @@ export function initPersonas() {
$('#avatar_upload_file').trigger('click'); $('#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) { $(document).on('click', '#user_avatar_block .set_persona_image', function (e) {
e.stopPropagation(); e.stopPropagation();
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile'); 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. * 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?} header - The header text for the popup.
* @param {string} text - The main text for the popup. * @param {string?} text - The main text for the popup.
* @param {string} [defaultValue=''] - The default value for the input field. * @param {string} [defaultValue=''] - The default value for the input field.
* @param {PopupOptions} [popupOptions={}] - Options for the popup. * @param {PopupOptions} [popupOptions={}] - Options for the popup.
* @return {Promise<string?>} A Promise that resolves with the user's input. * @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 * Builds popup content with header and text below
* *
* @param {string} header - The header to be added to the text * @param {string?} header - The header to be added to the text
* @param {string} text - The main text content * @param {string?} text - The main text content
*/ */
static BuildTextWithHeader(header, text) { static BuildTextWithHeader(header, text) {
if (!header) { if (!header) {
return text; return text;
} }
return `<h3>${header}</h3> 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('personality');
validateMissingField('persona'); validateMissingField('persona');
validateMissingField('scenario'); validateMissingField('scenario');
validateMissingField('system'); // validateMissingField('system');
validateMissingField('wiBefore', 'loreBefore'); validateMissingField('wiBefore', 'loreBefore');
validateMissingField('wiAfter', 'loreAfter'); validateMissingField('wiAfter', 'loreAfter');
@@ -2973,7 +2973,13 @@ function setAvgBG() {
return ''; 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 // @ts-ignore
const fuse = new Fuse(themes, { const fuse = new Fuse(themes, {
keys: [ keys: [
@@ -2981,12 +2987,12 @@ async function setThemeCallback(_, text) {
], ],
}); });
const results = fuse.search(text); const results = fuse.search(themeName);
console.debug('Theme fuzzy search results for ' + text, results); console.debug('Theme fuzzy search results for ' + themeName, results);
const theme = results[0]?.item; const theme = results[0]?.item;
if (!theme) { if (!theme) {
toastr.warning(`Could not find theme with name: ${text}`); toastr.warning(`Could not find theme with name: ${themeName}`);
return; 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); power_user.chat_width = Number(e.target.value);
localStorage.setItem(storage_keys.chat_width, power_user.chat_width); localStorage.setItem(storage_keys.chat_width, power_user.chat_width);
applyChatWidth(); applyChatWidth(applyMode);
setHotswapsDebounced(); setHotswapsDebounced();
}); });
@@ -3362,11 +3369,12 @@ $(document).ready(() => {
saveSettingsDebounced(); 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); power_user.font_scale = Number(e.target.value);
$('#font_scale_counter').val(power_user.font_scale); $('#font_scale_counter').val(power_user.font_scale);
localStorage.setItem(storage_keys.font_scale, power_user.font_scale); localStorage.setItem(storage_keys.font_scale, power_user.font_scale);
await applyFontScale(); await applyFontScale(applyMode);
saveSettingsDebounced(); saveSettingsDebounced();
}); });
@@ -4065,13 +4073,30 @@ $(document).ready(() => {
callback: setThemeCallback, callback: setThemeCallback,
unnamedArgumentList: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
description: 'name', description: 'theme name',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => themes.map(theme => new SlashCommandEnumValue(theme.name)), 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({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'movingui', name: 'movingui',

View File

@@ -142,9 +142,8 @@ export function initDefaultSlashCommands() {
returns: 'the current background', returns: 'the current background',
unnamedArgumentList: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
description: 'filename', description: 'background filename',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: () => [...document.querySelectorAll('.bg_example')] enumProvider: () => [...document.querySelectorAll('.bg_example')]
.map(it => new SlashCommandEnumValue(it.getAttribute('bgfile'))) .map(it => new SlashCommandEnumValue(it.getAttribute('bgfile')))
.filter(it => it.value?.length), .filter(it => it.value?.length),
@@ -154,12 +153,18 @@ export function initDefaultSlashCommands() {
<div> <div>
Sets a background according to the provided filename. Partial names allowed. Sets a background according to the provided filename. Partial names allowed.
</div> </div>
<div>
If no background is provided, this will return the currently selected background.
</div>
<div> <div>
<strong>Example:</strong> <strong>Example:</strong>
<ul> <ul>
<li> <li>
<pre><code>/bg beach.jpg</code></pre> <pre><code>/bg beach.jpg</code></pre>
</li> </li>
<li>
<pre><code>/bg</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,
@@ -1359,7 +1364,7 @@ export function initDefaultSlashCommands() {
enumProvider: commonEnumProviders.injects, enumProvider: commonEnumProviders.injects,
}), }),
new SlashCommandNamedArgument( 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( new SlashCommandNamedArgument(
'depth', 'injection depth', [ARGUMENT_TYPE.NUMBER], false, false, '4', 'depth', 'injection depth', [ARGUMENT_TYPE.NUMBER], false, false, '4',
@@ -1387,7 +1392,7 @@ export function initDefaultSlashCommands() {
'text', [ARGUMENT_TYPE.STRING], false, '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({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'listinjects', name: 'listinjects',
@@ -1504,6 +1509,7 @@ function injectCallback(args, value) {
'before': extension_prompt_types.BEFORE_PROMPT, 'before': extension_prompt_types.BEFORE_PROMPT,
'after': extension_prompt_types.IN_PROMPT, 'after': extension_prompt_types.IN_PROMPT,
'chat': extension_prompt_types.IN_CHAT, 'chat': extension_prompt_types.IN_CHAT,
'none': extension_prompt_types.NONE,
}; };
const roles = { const roles = {
'system': extension_prompt_roles.SYSTEM, 'system': extension_prompt_roles.SYSTEM,
@@ -2879,7 +2885,6 @@ export async function sendMessageAs(args, text) {
if (args.name) { if (args.name) {
name = args.name.trim(); name = args.name.trim();
mesText = text.trim();
if (!name && !text) { if (!name && !text) {
toastr.warning('You must specify a name and text to send as'); 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'); localStorage.setItem(namelessWarningKey, 'true');
} }
name = name2; 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 // Requires a regex check after the slash command is pushed to output
mesText = getRegexedString(mesText, regex_placement.SLASH_COMMAND, { characterOverride: name }); 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;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;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;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> </ul>
<div data-i18n="Chat variables Macros:"> <div data-i18n="Chat variables Macros:">
Chat variables Macros: Chat variables Macros:

View File

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

View File

@@ -31,7 +31,7 @@
<h3 data-i18n="Confused or lost?">Confused or lost?</h3> <h3 data-i18n="Confused or lost?">Confused or lost?</h3>
<ul> <ul>
<li> <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>
<li> <li>
<span data-i18n="Enter">Enter </span><code>/?</code><span data-i18n="in the chat bar"> in the chat bar</span> <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) { if (settings.type === HUGGINGFACE) {
params.top_p = Math.min(Math.max(Number(params.top_p), 0.0), 0.999); 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) : []; 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) { if (settings.type === MANCER) {

View File

@@ -704,6 +704,22 @@ export function isOdd(number) {
return number % 2 !== 0; 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(); const dateCache = new Map();
/** /**
@@ -717,26 +733,27 @@ export function timestampToMoment(timestamp) {
return dateCache.get(timestamp); return dateCache.get(timestamp);
} }
const moment = parseTimestamp(timestamp); const iso8601 = parseTimestamp(timestamp);
dateCache.set(timestamp, moment); const objMoment = iso8601 ? moment(iso8601) : moment.invalid();
return moment;
dateCache.set(timestamp, objMoment);
return objMoment;
} }
/** /**
* Parses a timestamp and returns a moment object representing the parsed date and time. * 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. * @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) { function parseTimestamp(timestamp) {
if (!timestamp) return moment.invalid(); if (!timestamp) return;
// Unix time (legacy TAI / tags) // Unix time (legacy TAI / tags)
if (typeof timestamp === 'number' || /^\d+$/.test(timestamp)) { if (typeof timestamp === 'number' || /^\d+$/.test(timestamp)) {
const number = Number(timestamp); const unixTime = Number(timestamp);
if (isNaN(number)) return moment.invalid(); const isValid = Number.isFinite(unixTime) && !Number.isNaN(unixTime) && unixTime >= 0;
if (!isFinite(number)) return moment.invalid(); if (!isValid) return;
if (number < 0) return moment.invalid(); return new Date(unixTime).toISOString();
return moment(number);
} }
let dtFmt = []; let dtFmt = [];
@@ -760,32 +777,12 @@ function parseTimestamp(timestamp) {
// 2024-6-5 @14h 56m 50s 682ms // 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/ }); 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) { for (const x of dtFmt) {
let rgxMatch = timestamp.match(x.pattern); let rgxMatch = timestamp.match(x.pattern);
if (!rgxMatch) continue; if (!rgxMatch) continue;
iso8601 = x.callback(...rgxMatch); return 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;
} }
/** Split string to parts no more than length in size. /** 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 * @param {boolean} [loadIfNotSelected=false] - Indicates whether to load the file even if it's not currently selected
*/ */
function reloadEditor(file, loadIfNotSelected = false) { function reloadEditor(file, loadIfNotSelected = false) {
const currentIndex = $('#world_editor_select').val(); const currentIndex = Number($('#world_editor_select').val());
const selectedIndex = world_names.indexOf(file); const selectedIndex = world_names.indexOf(file);
if (selectedIndex !== -1 && (loadIfNotSelected || currentIndex === selectedIndex)) { if (selectedIndex !== -1 && (loadIfNotSelected || currentIndex === selectedIndex)) {
$('#world_editor_select').val(selectedIndex).trigger('change'); $('#world_editor_select').val(selectedIndex).trigger('change');
@@ -1647,32 +1647,38 @@ function sortEntries(data) {
if (!data.length) return 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 we have a search term for WI, we are sorting by weighting scores
if (sortRule === 'search') { if (sortRule === 'search') {
data.sort((a, b) => { primarySort = (a, b) => {
const aScore = worldInfoFilter.getScore(FILTER_TYPES.WORLD_INFO_SEARCH, a.uid); const aScore = worldInfoFilter.getScore(FILTER_TYPES.WORLD_INFO_SEARCH, a.uid);
const bScore = worldInfoFilter.getScore(FILTER_TYPES.WORLD_INFO_SEARCH, b.uid); const bScore = worldInfoFilter.getScore(FILTER_TYPES.WORLD_INFO_SEARCH, b.uid);
return (aScore - bScore); return aScore - bScore;
}); };
} }
else if (sortRule === 'custom') { else if (sortRule === 'custom') {
// First by display index, then by order, then by uid // First by display index
data.sort((a, b) => { primarySort = (a, b) => {
const aValue = a.displayIndex; const aValue = a.displayIndex;
const bValue = b.displayIndex; const bValue = b.displayIndex;
return aValue - bValue;
return (aValue - bValue || b.order - a.order || a.uid - b.uid); };
});
} else if (sortRule === 'priority') { } else if (sortRule === 'priority') {
// First constant, then normal, then disabled. Then sort by order // First constant, then normal, then disabled.
data.sort((a, b) => { primarySort = (a, b) => {
const aValue = a.constant ? 0 : a.disable ? 2 : 1; const aValue = a.constant ? 0 : a.disable ? 2 : 1;
const bValue = b.constant ? 0 : b.disable ? 2 : 1; const bValue = b.constant ? 0 : b.disable ? 2 : 1;
return aValue - bValue;
return (aValue - bValue || b.order - a.order); };
});
} else { } else {
const primarySort = (a, b) => { primarySort = (a, b) => {
const aValue = a[sortField]; const aValue = a[sortField];
const bValue = b[sortField]; const bValue = b[sortField];
@@ -1690,26 +1696,12 @@ function sortEntries(data) {
// Sort numbers // Sort numbers
return orderSign * (Number(aValue) - Number(bValue)); 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; 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 entryCount = Object.keys(data.entries).length;
const moreThan100 = entryCount > 100; 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) { 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>`; 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; if (!result) return;
const start = Number(result); const start = Number(result);
if (isNaN(start) || start < 0) { if (isNaN(start) || start < 0) {
toastr.error('Invalid number: ' + result, 'Apply Custom Sorting'); toastr.error('Invalid number: ' + result, 'Apply Current Sorting');
return; return;
} }
if (start < entryCount) { 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; // We need to sort the entries here, as the data source isn't sorted
for (const entry of Object.values(data.entries)) { const entries = Object.values(data.entries);
const newOrder = Math.max(start - (entry.displayIndex ?? 0), 0); sortEntries(entries);
let updated = 0, current = start;
for (const entry of entries) {
const newOrder = Math.max(current--, 0);
if (entry.order === newOrder) continue; if (entry.order === newOrder) continue;
entry.order = newOrder; entry.order = newOrder;
setOriginalDataValue(data, entry.order, 'order', entry.order); setOriginalDataValue(data, entry.order, 'order', entry.order);
counter++; updated++;
} }
if (counter > 0) { if (updated > 0) {
toastr.info(`Updated ${counter} Order values`, 'Apply Custom Sorting'); toastr.info(`Updated ${updated} Order values`, 'Apply Custom Sorting');
await saveWorldInfo(name, data, true); await saveWorldInfo(name, data, true);
updateEditor(navigation_option.previous); updateEditor(navigation_option.previous);
} else { } else {

View File

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

View File

@@ -1,4 +1,5 @@
require('./polyfill.js'); require('./polyfill.js');
const { getConfigValue } = require('./util.js');
/** /**
* Convert a prompt from the ChatML objects to the format used by Claude. * 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 // Make the last assistant message a prefill
const prefixEnabled = getConfigValue('mistral.enablePrefix', false);
const lastMsg = messages[messages.length - 1]; 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; lastMsg.prefix = true;
} }