mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-02 10:57:45 +01:00
Merge branch 'staging' into parser-followup-2
This commit is contained in:
commit
2470f775e2
@ -93,6 +93,11 @@ openai:
|
||||
deepl:
|
||||
# Available options: default, more, less, prefer_more, prefer_less
|
||||
formality: default
|
||||
# -- MISTRAL API CONFIGURATION --
|
||||
mistral:
|
||||
# Enables prefilling of the reply with the last assistant message in the prompt
|
||||
# CAUTION: The prefix is echoed into the completion. You may want to use regex to trim it out.
|
||||
enablePrefix: false
|
||||
# -- SERVER PLUGIN CONFIGURATION --
|
||||
enableServerPlugins: false
|
||||
# User session timeout *in seconds* (defaults to 24 hours).
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sillytavern",
|
||||
"version": "1.12.3",
|
||||
"version": "1.12.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sillytavern",
|
||||
"version": "1.12.3",
|
||||
"version": "1.12.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
@ -70,7 +70,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
||||
},
|
||||
"version": "1.12.3",
|
||||
"version": "1.12.4",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"start:no-csrf": "node server.js --disableCsrf",
|
||||
|
@ -89,7 +89,7 @@
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100svh;
|
||||
height: 100dvh;
|
||||
z-index: 9998;
|
||||
top: 0;
|
||||
}
|
||||
|
@ -7,7 +7,8 @@
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.extensions_block input[type="checkbox"] {
|
||||
.extensions_block input[type="checkbox"],
|
||||
.extensions_block input[type="radio"] {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
@ -7,8 +7,8 @@
|
||||
z-index: 999999;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
width: 100svw;
|
||||
height: 100svh;
|
||||
width: 100dvw;
|
||||
height: 100dvh;
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
/*for some reason the full screen blur does not work on iOS*/
|
||||
|
@ -1,7 +1,7 @@
|
||||
#logprobsViewer {
|
||||
overflow-y: auto;
|
||||
max-width: 90svw;
|
||||
max-height: 90svh;
|
||||
max-width: 90dvw;
|
||||
max-height: 90dvh;
|
||||
min-width: 100px;
|
||||
min-height: 50px;
|
||||
border-radius: 10px;
|
||||
@ -16,7 +16,7 @@
|
||||
top: 0;
|
||||
margin: 0;
|
||||
right: unset;
|
||||
width: calc(((100svw - var(--sheldWidth)) / 2) - 1px);
|
||||
width: calc(((100dvw - var(--sheldWidth)) / 2) - 1px);
|
||||
}
|
||||
|
||||
.logprobs_panel_header {
|
||||
|
@ -1,6 +1,8 @@
|
||||
/*will apply to anything 1000px or less. this catches ipads, horizontal phones, and vertical phones)*/
|
||||
@media screen and (max-width: 1000px) {
|
||||
#send_form.compact #leftSendForm, #send_form.compact #rightSendForm {
|
||||
|
||||
#send_form.compact #leftSendForm,
|
||||
#send_form.compact #rightSendForm {
|
||||
flex-wrap: nowrap;
|
||||
width: unset;
|
||||
}
|
||||
@ -34,9 +36,9 @@
|
||||
right: 0;
|
||||
width: fit-content;
|
||||
max-height: calc(60vh - 60px);
|
||||
max-height: calc(60svh - 60px);
|
||||
max-height: calc(60dvh - 60px);
|
||||
max-width: 90vw;
|
||||
max-width: 90svw;
|
||||
max-width: 90dvw;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
@ -102,7 +104,7 @@
|
||||
min-width: unset;
|
||||
width: 100%;
|
||||
max-height: calc(100vh - 45px);
|
||||
max-height: calc(100svh - 45px);
|
||||
max-height: calc(100dvh - 45px);
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 5px;
|
||||
@ -130,15 +132,15 @@
|
||||
#top-bar {
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
width: 100svw;
|
||||
width: 100dvw;
|
||||
}
|
||||
|
||||
#bg1,
|
||||
#bg_custom {
|
||||
height: 100vh !important;
|
||||
height: 100svh !important;
|
||||
height: 100dvh !important;
|
||||
width: 100vw !important;
|
||||
width: 100svw !important;
|
||||
width: 100dvw !important;
|
||||
background-position: center;
|
||||
|
||||
}
|
||||
@ -146,13 +148,7 @@
|
||||
|
||||
#sheld,
|
||||
#character_popup,
|
||||
.drawer-content
|
||||
|
||||
/* ,
|
||||
#world_popup */
|
||||
{
|
||||
/*max-height: calc(100vh - 36px);
|
||||
max-height: calc(100svh - 36px);*/
|
||||
.drawer-content {
|
||||
width: 100% !important;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
@ -223,10 +219,10 @@
|
||||
#floatingPrompt,
|
||||
#cfgConfig,
|
||||
#logprobsViewer,
|
||||
#movingDivs > div {
|
||||
/* 100vh are fallback units for browsers that don't support svh */
|
||||
#movingDivs>div {
|
||||
/* 100vh are fallback units for browsers that don't support dvh */
|
||||
height: calc(100vh - 45px);
|
||||
height: calc(100svh - 45px);
|
||||
height: calc(100dvh - 45px);
|
||||
min-width: 100% !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
@ -249,7 +245,7 @@
|
||||
#floatingPrompt,
|
||||
#cfgConfig,
|
||||
#logprobsViewer,
|
||||
#movingDivs > div {
|
||||
#movingDivs>div {
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
@ -286,9 +282,9 @@
|
||||
|
||||
body.waifuMode #sheld {
|
||||
height: 40vh;
|
||||
height: 40svh;
|
||||
height: 40dvh;
|
||||
top: 60vh;
|
||||
top: 60svh;
|
||||
top: 60dvh;
|
||||
bottom: 0 !important;
|
||||
}
|
||||
|
||||
@ -325,16 +321,16 @@
|
||||
body.waifuMode .zoomed_avatar {
|
||||
width: fit-content;
|
||||
max-height: calc(60vh - 60px);
|
||||
max-height: calc(60svh - 60px);
|
||||
max-height: calc(60dvh - 60px);
|
||||
max-width: 90vw;
|
||||
max-width: 90svw;
|
||||
max-width: 90dvw;
|
||||
}
|
||||
|
||||
.scrollableInner {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: calc(100vh - 90px);
|
||||
max-height: calc(100svh - 90px);
|
||||
max-height: calc(100dvh - 90px);
|
||||
}
|
||||
|
||||
.horde_multiple_hint {
|
||||
@ -370,9 +366,9 @@
|
||||
|
||||
body:not(.waifuMode) .zoomed_avatar {
|
||||
max-height: calc(60vh - 60px);
|
||||
max-height: calc(60svh - 60px);
|
||||
max-height: calc(60dvh - 60px);
|
||||
max-width: 90vw;
|
||||
max-width: 90svw;
|
||||
max-width: 90dvw;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
@ -453,9 +449,9 @@
|
||||
min-height: unset;
|
||||
max-height: unset;
|
||||
width: 100vw;
|
||||
width: 100svw;
|
||||
width: 100dvw;
|
||||
height: calc(100vh - 36px);
|
||||
height: calc(100svh - 36px);
|
||||
height: calc(100dvh - 36px);
|
||||
padding-right: max(env(safe-area-inset-right), 0px);
|
||||
padding-left: max(env(safe-area-inset-left), 0px);
|
||||
padding-bottom: 0;
|
||||
@ -485,10 +481,10 @@
|
||||
top: 0;
|
||||
margin: 0 auto;
|
||||
height: calc(100vh - 70px);
|
||||
height: calc(100svh - 70px);
|
||||
height: calc(100dvh - 70px);
|
||||
width: calc(100% - 5px);
|
||||
max-height: calc(100vh - 70px);
|
||||
max-height: calc(100svh - 70px);
|
||||
max-height: calc(100dvh - 70px);
|
||||
max-width: calc(100% - 5px);
|
||||
|
||||
}
|
||||
|
@ -7,5 +7,5 @@ body.safari .popup.large_dialogue_popup .popup-body {
|
||||
body.safari .popup .popup-body {
|
||||
height: fit-content;
|
||||
max-height: 90vh;
|
||||
max-height: 90svh;
|
||||
max-height: 90dvh;
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
max-height: calc(100svh - 2em);
|
||||
max-width: calc(100svw - 2em);
|
||||
max-height: calc(100dvh - 2em);
|
||||
max-width: calc(100dvw - 2em);
|
||||
min-height: fit-content;
|
||||
|
||||
/* Overflow visible so elements (like toasts) can appear outside of the dialog. '.popup-body' is hiding overflow for the real content. */
|
||||
@ -103,7 +103,7 @@ body.no-blur .popup[open]::backdrop {
|
||||
|
||||
.popup #toast-container {
|
||||
/* Fix toastr in dialogs by actually placing it at the top of the screen via transform */
|
||||
height: 100svh;
|
||||
height: 100dvh;
|
||||
top: calc(50% + var(--topBarBlockSize));
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
@ -115,7 +115,7 @@ body.no-blur .popup[open]::backdrop {
|
||||
.popup-crop-wrap {
|
||||
margin: 10px auto;
|
||||
max-height: 75vh;
|
||||
max-height: 75svh;
|
||||
max-height: 75dvh;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
@ -364,7 +364,7 @@ body.waifuMode #top-bar {
|
||||
|
||||
body.waifuMode #sheld {
|
||||
height: 40vh;
|
||||
height: 40svh;
|
||||
height: 40dvh;
|
||||
top: calc(100% - 40vh);
|
||||
bottom: 0;
|
||||
}
|
||||
|
2
public/global.d.ts
vendored
2
public/global.d.ts
vendored
@ -16,6 +16,8 @@ declare var ai;
|
||||
|
||||
// Jquery plugins
|
||||
interface JQuery {
|
||||
nanogallery2(options?: any): JQuery;
|
||||
nanogallery2(method: string, options?: any): JQuery;
|
||||
pagination(method: 'getCurrentPageNum'): number;
|
||||
pagination(method: string, options?: any): JQuery;
|
||||
pagination(options?: any): JQuery;
|
||||
|
@ -3296,6 +3296,24 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class="flex1" title="Inserted before the first User's message." data-i18n="[title]Inserted before the first User's message.">
|
||||
<label for="instruct_first_input_sequence">
|
||||
<small data-i18n="First User Prefix">First User Prefix</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_first_input_sequence" class="text_pole textarea_compact autoSetHeight" maxlength="2000" placeholder="—" 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="—" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class="flex1" title="Will be inserted as a last prompt line when using system/neutral generation." data-i18n="[title]Will be inserted as a last prompt line when using system/neutral generation.">
|
||||
<label for="instruct_last_system_sequence">
|
||||
@ -3644,8 +3662,8 @@
|
||||
<div id="OpenAllWIEntries" class="menu_button fa-solid fa-expand" title="Open all Entries" data-i18n="[title]Open all Entries"></div>
|
||||
<div id="CloseAllWIEntries" class="menu_button fa-solid fa-compress" title="Close all Entries" data-i18n="[title]Close all Entries"></div>
|
||||
<div id="world_popup_new" class="menu_button fa-solid fa-plus" title="New Entry" data-i18n="[title]New Entry"></div>
|
||||
<div id="world_backfill_memos" class="menu_button fa-solid fa-notes-medical" title="Fill empty Memo/Titles with Keywords" data-i18n="[title]Fill empty Memo/Titles with Keywords"></div><div id="world_apply_custom_sorting" class="menu_button fa-solid fa-solid fa-arrow-down-9-1"
|
||||
title="Apply custom sorting as Order" data-i18n="[title]Apply custom sorting as Order"></div>
|
||||
<div id="world_backfill_memos" class="menu_button fa-solid fa-notes-medical" title="Fill empty Memo/Titles with Keywords" data-i18n="[title]Fill empty Memo/Titles with Keywords"></div>
|
||||
<div id="world_apply_current_sorting" class="menu_button fa-solid fa-solid fa-arrow-down-9-1" title="Apply current sorting as Order" data-i18n="[title]Apply current sorting as Order"></div>
|
||||
<div id="world_import_button" class="menu_button fa-solid fa-file-import" title="Import World Info" data-i18n="[title]Import World Info"></div>
|
||||
<div id="world_popup_export" class="menu_button fa-solid fa-file-export" title="Export World Info" data-i18n="[title]Export World Info"></div>
|
||||
<div id="world_duplicate" class="menu_button fa-solid fa-paste" title="Duplicate World Info" data-i18n="[title]Duplicate World Info"></div>
|
||||
@ -4008,7 +4026,7 @@
|
||||
<input type="range" id="smooth_streaming_speed" name="smooth_streaming_speed" min="0" max="100" step="10" value="50">
|
||||
<div class="slider_hint">
|
||||
<span data-i18n="Slow">Slow</span>
|
||||
<span data-i18n=""></span>
|
||||
<span></span>
|
||||
<span data-i18n="Fast">Fast</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -6450,6 +6468,9 @@
|
||||
<button class="menu_button set_default_persona" title="Select this as default persona for the new chats." data-i18n="[title]Select this as default persona for the new chats.">
|
||||
<i class="fa-fw fa-solid fa-crown"></i>
|
||||
</button>
|
||||
<button class="menu_button duplicate_persona" title="Duplicate persona" data-i18n="[title]Duplicate persona">
|
||||
<i class="fa-fw fa-solid fa-clone"></i>
|
||||
</button>
|
||||
<button class="menu_button delete_avatar" title="Delete persona" data-i18n="[title]Delete persona">
|
||||
<i class="fa-fw fa-solid fa-trash-alt"></i>
|
||||
</button>
|
||||
|
@ -217,9 +217,11 @@
|
||||
"Character Names Behavior": "角色名称行为",
|
||||
"Helps the model to associate messages with characters.": "有助于模型将消息与角色关联起来。",
|
||||
"None": "无",
|
||||
"character_names_none": "不添加角色名称前缀。在群聊中可能导致错误行为,谨慎勾选。",
|
||||
"Never add character names.": "不添加角色名称。",
|
||||
"character_names_default": "群聊和过去的角色除外。否则,请确保在提示词中提供了姓名。",
|
||||
"Don't add character names.": "不添加角色名称。",
|
||||
"Completion": "补全对象",
|
||||
"Don't add character names unless necessary.": "如非必要,否则不添加角色名称。",
|
||||
"Completion Object": "补全对象",
|
||||
"character_names_completion": "适用限制:仅限拉丁字母数字和下划线。不适用于所有补全源,尤其是:Claude、MistralAI、Google。",
|
||||
"Add character names to completion objects.": "在补全对象中添加角色名称。",
|
||||
"Message Content": "消息内容",
|
||||
@ -489,6 +491,10 @@
|
||||
"First Assistant Prefix": "第一个助理前缀",
|
||||
"instruct_last_output_sequence": "插入到最后一条助手消息之前或作为生成 AI 回复时的最后一行提示词(中立/系统角色除外)。",
|
||||
"Last Assistant Prefix": "最后一个助理前缀",
|
||||
"Inserted before the first User's message.": "插入在第一个用户的消息之前。",
|
||||
"First User Prefix": "第一个用户前缀",
|
||||
"instruct_last_input_sequence": "插入到最后一条用户消息之前。",
|
||||
"Last User Prefix": "上次用户前缀",
|
||||
"Will be inserted as a last prompt line when using system/neutral generation.": "当使用系统/中性生成时将作为最后的一行提示词插入。",
|
||||
"System Instruction Prefix": "系统指令前缀",
|
||||
"If a stop sequence is generated, everything past it will be removed from the output (inclusive).": "如果生成了停止序列,则该序列之后的所有内容都将从输出中删除(包括在内)。",
|
||||
@ -555,7 +561,7 @@
|
||||
"Close all Entries": "关闭所有条目",
|
||||
"New Entry": "新条目",
|
||||
"Fill empty Memo/Titles with Keywords": "使用关键字填充空的备忘录/标题",
|
||||
"Apply custom sorting as Order": "应用自定义排序作为顺序",
|
||||
"Apply current sorting as Order": "应用当前排序作为顺序",
|
||||
"Import World Info": "导入世界书",
|
||||
"Export World Info": "导出世界书",
|
||||
"Duplicate World Info": "复制世界书",
|
||||
@ -1187,6 +1193,7 @@
|
||||
"Bind user name to that avatar": "将用户名称绑定到该头像",
|
||||
"Change persona image": "更改用户角色头像",
|
||||
"Select this as default persona for the new chats.": "选择此项作为新聊天的默认用户角色。",
|
||||
"Duplicate persona": "复制用户角色",
|
||||
"Delete persona": "删除用户角色",
|
||||
"These characters are the winners of character design contests and have outstandable quality.": "这些角色都是角色设计大赛的获奖者,品质非常出色。",
|
||||
"Contest Winners": "比赛获胜者",
|
||||
@ -1327,6 +1334,10 @@
|
||||
"ext_sum_injection_template": "插入模板",
|
||||
"ext_sum_memory_template_placeholder": "{{summary}} 将解析当前摘要内容。",
|
||||
"ext_sum_injection_position": "插入位置",
|
||||
"ext_sum_include_wi_scan_desc": "在 WI 扫描中包括最新摘要。",
|
||||
"ext_sum_include_wi_scan": "纳入世界信息扫描",
|
||||
"None (not injected)": "无(未注入)",
|
||||
"ext_sum_injection_position_none": "摘要不会被注入到提示中。您仍然可以通过 {{summary}} 宏访问它。",
|
||||
"How many messages before the current end of the chat.": "当前聊天结束前还有多少条消息。",
|
||||
"Labels and Message": "标签和信息",
|
||||
"Label": "标签",
|
||||
@ -1757,6 +1768,8 @@
|
||||
"help_macros_56": "指示系统指令前缀",
|
||||
"help_macros_57": "指示第一个用户消息填充器",
|
||||
"help_macros_58": "指示停止顺序",
|
||||
"help_macros_first_user": "指示用户第一个输入序列",
|
||||
"help_macros_last_user": "指示用户最后输入序列",
|
||||
"Chat variables Macros:": "聊天变量宏:",
|
||||
"Local variables = unique to the current chat": "局部变量 = 当前聊天所独有",
|
||||
"Global variables = works in any chat for any character": "全局变量 = 适用于任何角色的任何聊天",
|
||||
@ -1794,6 +1807,7 @@
|
||||
"Record a snapshot of your current settings.": "记录当前设置的快照。",
|
||||
"Make a Snapshot": "制作快照",
|
||||
"Restore this snapshot": "恢复此快照",
|
||||
"Download Model": "下载模型",
|
||||
"Downloader Options": "下载器选项",
|
||||
"Extra parameters for downloading/HuggingFace API": "下载/HuggingFace API 的额外参数。如果不确定,请将其留空。",
|
||||
"Revision": "修订",
|
||||
|
@ -570,6 +570,7 @@ export const system_message_types = {
|
||||
* @enum {number} Extension prompt types
|
||||
*/
|
||||
export const extension_prompt_types = {
|
||||
NONE: -1,
|
||||
IN_PROMPT: 0,
|
||||
IN_CHAT: 1,
|
||||
BEFORE_PROMPT: 2,
|
||||
@ -929,10 +930,17 @@ async function firstLoadInit() {
|
||||
initCfg();
|
||||
initLogprobs();
|
||||
doDailyExtensionUpdatesCheck();
|
||||
hideLoader();
|
||||
await hideLoader();
|
||||
await fixViewport();
|
||||
await eventSource.emit(event_types.APP_READY);
|
||||
}
|
||||
|
||||
async function fixViewport() {
|
||||
document.body.style.position = 'absolute';
|
||||
await delay(1);
|
||||
document.body.style.position = '';
|
||||
}
|
||||
|
||||
function cancelStatusCheck() {
|
||||
abortStatusCheck?.abort();
|
||||
abortStatusCheck = new AbortController();
|
||||
@ -3611,6 +3619,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
let chat2 = [];
|
||||
let continue_mag = '';
|
||||
const userMessageIndices = [];
|
||||
const lastUserMessageIndex = coreChat.findLastIndex(x => x.is_user);
|
||||
|
||||
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
|
||||
if (main_api == 'openai') {
|
||||
@ -3629,6 +3638,11 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST);
|
||||
}
|
||||
|
||||
if (lastUserMessageIndex >= 0 && j === lastUserMessageIndex && isInstruct) {
|
||||
// Reformat with the last input sequence (if any)
|
||||
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST);
|
||||
}
|
||||
|
||||
// Do not suffix the message for continuation
|
||||
if (i === 0 && isContinue) {
|
||||
if (isInstruct) {
|
||||
@ -3654,7 +3668,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
mes: power_user.instruct.user_alignment_message,
|
||||
is_user: true,
|
||||
};
|
||||
userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, false);
|
||||
userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, force_output_sequence.FIRST);
|
||||
}
|
||||
|
||||
// Call combined AN into Generate
|
||||
@ -6053,9 +6067,10 @@ async function getChatResult() {
|
||||
const message = getFirstMessage();
|
||||
if (message.mes) {
|
||||
chat.push(message);
|
||||
await saveChatConditional();
|
||||
freshChat = true;
|
||||
}
|
||||
// Make sure the chat appears on the server
|
||||
await saveChatConditional();
|
||||
}
|
||||
await loadItemizedPrompts(getCurrentChatId());
|
||||
await printMessages();
|
||||
@ -6107,7 +6122,7 @@ export async function openCharacterChat(file_name) {
|
||||
chat_metadata = {};
|
||||
await getChat();
|
||||
$('#selected_chat_pole').val(file_name);
|
||||
await createOrEditCharacter();
|
||||
await createOrEditCharacter(new CustomEvent('newChat'));
|
||||
}
|
||||
|
||||
////////// OPTIMZED MAIN API CHANGE FUNCTION ////////////
|
||||
@ -6809,6 +6824,11 @@ export async function displayPastChats() {
|
||||
const fileName = chat['file_name'];
|
||||
const chatContent = rawChats[fileName];
|
||||
|
||||
// Make sure empty chats are displayed when there is no search query
|
||||
if (Array.isArray(chatContent) && !chatContent.length && !searchQuery) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// // Uncomment this to return to old behavior (classical full-substring search).
|
||||
// return chatContent && Object.values(chatContent).some(message => message?.mes?.toLowerCase()?.includes(searchQuery.toLowerCase()));
|
||||
|
||||
@ -8436,6 +8456,11 @@ const CONNECT_API_MAP = {
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.OPENROUTER,
|
||||
},
|
||||
'featherless': {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.FEATHERLESS,
|
||||
},
|
||||
'huggingface': {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
@ -9837,8 +9862,8 @@ jQuery(async function () {
|
||||
hideMenu();
|
||||
});
|
||||
|
||||
$('#newChatFromManageScreenButton').on('click', function () {
|
||||
doNewChat({ deleteCurrentChat: false });
|
||||
$('#newChatFromManageScreenButton').on('click', async function () {
|
||||
await doNewChat({ deleteCurrentChat: false });
|
||||
$('#select_chat_cross').trigger('click');
|
||||
});
|
||||
|
||||
@ -10810,7 +10835,7 @@ jQuery(async function () {
|
||||
//newSlider.val(manualInput)
|
||||
//handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual');
|
||||
valueBeforeManualInput = manualInput;
|
||||
$(masterElement).val($(this).val()).trigger('input');
|
||||
$(masterElement).val($(this).val()).trigger('input', { forced: true });
|
||||
} else {
|
||||
//if value not ok, warn and reset to last known valid value
|
||||
toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`);
|
||||
@ -10836,7 +10861,7 @@ jQuery(async function () {
|
||||
if (manualInput >= Number($(this).attr('min')) && manualInput <= Number($(this).attr('max'))) {
|
||||
valueBeforeManualInput = manualInput;
|
||||
//set the slider value to input value
|
||||
$(masterElement).val($(this).val()).trigger('input');
|
||||
$(masterElement).val($(this).val()).trigger('input', { forced: true });
|
||||
} else {
|
||||
//if value not ok, warn and reset to last known valid value
|
||||
toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`);
|
||||
|
@ -1387,7 +1387,8 @@ async function getExpressionsList() {
|
||||
}
|
||||
|
||||
// If there was no specific list, or an error, just return the default expressions
|
||||
return DEFAULT_EXPRESSIONS;
|
||||
expressionsList = DEFAULT_EXPRESSIONS.filter(e => e !== 'talkinghead').slice();
|
||||
return expressionsList;
|
||||
}
|
||||
|
||||
const result = await resolveExpressionsList();
|
||||
@ -1618,11 +1619,13 @@ async function onClickExpressionRemoveCustom() {
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
function onExperesionApiChanged() {
|
||||
function onExpressionApiChanged() {
|
||||
const tempApi = this.value;
|
||||
if (tempApi) {
|
||||
extension_settings.expressions.api = Number(tempApi);
|
||||
$('.expression_llm_prompt_block').toggle(extension_settings.expressions.api === EXPRESSION_API.llm);
|
||||
expressionsList = null;
|
||||
spriteCache = {};
|
||||
moduleWorker();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
@ -1972,7 +1975,7 @@ function migrateSettings() {
|
||||
$('#expression_custom_add').on('click', onClickExpressionAddCustom);
|
||||
$('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
|
||||
$('#expression_fallback').on('change', onExpressionFallbackChanged);
|
||||
$('#expression_api').on('change', onExperesionApiChanged);
|
||||
$('#expression_api').on('change', onExpressionApiChanged);
|
||||
}
|
||||
|
||||
// Pause Talkinghead to save resources when the ST tab is not visible or the window is minimized.
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
this_chid,
|
||||
characters,
|
||||
getRequestHeaders,
|
||||
event_types,
|
||||
} from '../../../script.js';
|
||||
import { groups, selected_group } from '../../group-chats.js';
|
||||
import { loadFileToDocument, delay } from '../../utils.js';
|
||||
@ -25,6 +26,27 @@ let paginationVisiblePages = 10;
|
||||
let paginationMaxLinesPerPage = 2;
|
||||
let galleryMaxRows = 3;
|
||||
|
||||
$('body').on('click', '.dragClose', function () {
|
||||
const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
|
||||
$(`body > .draggable[id="${relatedId}"]`).remove(); // Remove the associated draggable
|
||||
});
|
||||
|
||||
const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved';
|
||||
|
||||
const mutationObserver = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
mutation.removedNodes.forEach((node) => {
|
||||
if (node instanceof HTMLElement && node.tagName === 'DIV' && node.id === 'gallery') {
|
||||
eventSource.emit(CUSTOM_GALLERY_REMOVED_EVENT);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
mutationObserver.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* Retrieves a list of gallery items based on a given URL. This function calls an API endpoint
|
||||
@ -59,7 +81,9 @@ async function getGalleryItems(url) {
|
||||
* @returns {Promise<void>} - Promise representing the completion of the gallery initialization.
|
||||
*/
|
||||
async function initGallery(items, url) {
|
||||
const nonce = `nonce-${Math.random().toString(36).substring(2, 15)}`;
|
||||
const gallery = $('#dragGallery');
|
||||
gallery.addClass(nonce);
|
||||
gallery.nanogallery2({
|
||||
'items': items,
|
||||
thumbnailWidth: 'auto',
|
||||
@ -82,16 +106,26 @@ async function initGallery(items, url) {
|
||||
fnThumbnailOpen: viewWithDragbox,
|
||||
});
|
||||
|
||||
|
||||
eventSource.on('resizeUI', function (elmntName) {
|
||||
gallery.nanogallery2('resize');
|
||||
});
|
||||
|
||||
const dragDropHandler = new DragAndDropHandler('#dragGallery', async (files, event) => {
|
||||
const dragDropHandler = new DragAndDropHandler(`#dragGallery.${nonce}`, async (files, event) => {
|
||||
let file = files[0];
|
||||
uploadFile(file, url); // Added url parameter to know where to upload
|
||||
});
|
||||
|
||||
const resizeHandler = function () {
|
||||
gallery.nanogallery2('resize');
|
||||
};
|
||||
|
||||
eventSource.on('resizeUI', resizeHandler);
|
||||
|
||||
eventSource.once(event_types.CHAT_CHANGED, function () {
|
||||
gallery.closest('#gallery').remove();
|
||||
});
|
||||
|
||||
eventSource.once(CUSTOM_GALLERY_REMOVED_EVENT, function () {
|
||||
gallery.nanogallery2('destroy');
|
||||
dragDropHandler.destroy();
|
||||
eventSource.removeListener('resizeUI', resizeHandler);
|
||||
});
|
||||
|
||||
// Set dropzone height to be the same as the parent
|
||||
gallery.css('height', gallery.parent().css('height'));
|
||||
@ -140,16 +174,10 @@ async function showCharGallery() {
|
||||
|
||||
const items = await getGalleryItems(url);
|
||||
// if there already is a gallery, destroy it and place this one in its place
|
||||
if ($('#dragGallery').length) {
|
||||
$('#dragGallery').nanogallery2('destroy');
|
||||
initGallery(items, url);
|
||||
} else {
|
||||
makeMovable();
|
||||
setTimeout(async () => {
|
||||
await initGallery(items, url);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
$('#dragGallery').closest('#gallery').remove();
|
||||
makeMovable();
|
||||
await delay(100);
|
||||
await initGallery(items, url);
|
||||
} catch (err) {
|
||||
console.trace();
|
||||
console.error(err);
|
||||
@ -202,11 +230,11 @@ async function uploadFile(file, url) {
|
||||
toastr.success('File uploaded successfully. Saved at: ' + result.path);
|
||||
|
||||
// Refresh the gallery
|
||||
$('#dragGallery').nanogallery2('destroy'); // Destroy old gallery
|
||||
const newItems = await getGalleryItems(url); // Fetch the latest items
|
||||
initGallery(newItems, url); // Reinitialize the gallery with new items and pass 'url'
|
||||
|
||||
|
||||
$('#dragGallery').closest('#gallery').remove(); // Destroy old gallery
|
||||
makeMovable();
|
||||
await delay(100);
|
||||
await initGallery(newItems, url); // Reinitialize the gallery with new items and pass 'url'
|
||||
} catch (error) {
|
||||
console.error('There was an issue uploading the file:', error);
|
||||
|
||||
@ -273,11 +301,6 @@ function makeMovable(id = 'gallery') {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('body').on('click', '.dragClose', function () {
|
||||
const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
|
||||
$(`#${relatedId}`).remove(); // Remove the associated draggable
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -358,11 +381,6 @@ function makeDragImg(id, url) {
|
||||
} else {
|
||||
console.error('Failed to append the template content or retrieve the appended content.');
|
||||
}
|
||||
|
||||
$('body').on('click', '.dragClose', function () {
|
||||
const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
|
||||
$(`#${relatedId}`).remove(); // Remove the associated draggable
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -401,7 +419,8 @@ function viewWithDragbox(items) {
|
||||
|
||||
|
||||
// Registers a simple command for opening the char gallery.
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery',
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'show-gallery',
|
||||
aliases: ['sg'],
|
||||
callback: () => {
|
||||
showCharGallery();
|
||||
@ -409,7 +428,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery
|
||||
},
|
||||
helpString: 'Shows the gallery.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery',
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'list-gallery',
|
||||
aliases: ['lg'],
|
||||
callback: listGalleryCommand,
|
||||
returns: 'list of images',
|
||||
@ -432,14 +452,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery
|
||||
|
||||
async function listGalleryCommand(args) {
|
||||
try {
|
||||
let url = args.char ?? (args.group ? groups.find(it=>it.name == args.group)?.id : null) ?? (selected_group || this_chid);
|
||||
let url = args.char ?? (args.group ? groups.find(it => it.name == args.group)?.id : null) ?? (selected_group || this_chid);
|
||||
if (!args.char && !args.group && !selected_group && this_chid) {
|
||||
const char = characters[this_chid];
|
||||
url = char.avatar.replace('.png', '');
|
||||
}
|
||||
|
||||
const items = await getGalleryItems(url);
|
||||
return JSON.stringify(items.map(it=>it.src));
|
||||
return JSON.stringify(items.map(it => it.src));
|
||||
|
||||
} catch (err) {
|
||||
console.trace();
|
||||
|
@ -5,7 +5,7 @@
|
||||
"optional": [
|
||||
],
|
||||
"js": "index.js",
|
||||
"css": "",
|
||||
"css": "style.css",
|
||||
"author": "City-Unit",
|
||||
"version": "1.5.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
|
5
public/scripts/extensions/gallery/style.css
Normal file
5
public/scripts/extensions/gallery/style.css
Normal file
@ -0,0 +1,5 @@
|
||||
.nGY2 .nGY2GalleryBottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
@ -14,6 +14,7 @@ import {
|
||||
substituteParamsExtended,
|
||||
generateRaw,
|
||||
getMaxContextSize,
|
||||
setExtensionPrompt,
|
||||
} from '../../../script.js';
|
||||
import { is_group_generating, selected_group } from '../../group-chats.js';
|
||||
import { loadMovingUIState } from '../../power-user.js';
|
||||
@ -73,6 +74,7 @@ const defaultSettings = {
|
||||
template: defaultTemplate,
|
||||
position: extension_prompt_types.IN_PROMPT,
|
||||
role: extension_prompt_roles.SYSTEM,
|
||||
scan: false,
|
||||
depth: 2,
|
||||
promptWords: 200,
|
||||
promptMinWords: 25,
|
||||
@ -122,6 +124,7 @@ function loadSettings() {
|
||||
$(`input[name="memory_prompt_builder"][value="${extension_settings.memory.prompt_builder}"]`).prop('checked', true).trigger('input');
|
||||
$('#memory_override_response_length').val(extension_settings.memory.overrideResponseLength).trigger('input');
|
||||
$('#memory_max_messages_per_request').val(extension_settings.memory.maxMessagesPerRequest).trigger('input');
|
||||
$('#memory_include_wi_scan').prop('checked', extension_settings.memory.scan).trigger('input');
|
||||
switchSourceControls(extension_settings.memory.source);
|
||||
}
|
||||
|
||||
@ -279,6 +282,13 @@ function onMemoryPositionChange(e) {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryIncludeWIScanInput() {
|
||||
const value = !!$(this).prop('checked');
|
||||
extension_settings.memory.scan = value;
|
||||
reinsertMemory();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryPromptWordsForceInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.promptForceWords = Number(value);
|
||||
@ -800,8 +810,7 @@ function reinsertMemory() {
|
||||
* @param {number|null} index Index of the chat message to save the summary to. If null, the pre-last message is used.
|
||||
*/
|
||||
function setMemoryContext(value, saveToMessage, index = null) {
|
||||
const context = getContext();
|
||||
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, false, extension_settings.memory.role);
|
||||
setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, extension_settings.memory.scan, extension_settings.memory.role);
|
||||
$('#memory_contents').val(value);
|
||||
|
||||
const summaryLog = value
|
||||
@ -809,6 +818,7 @@ function setMemoryContext(value, saveToMessage, index = null) {
|
||||
: 'Summary has no content';
|
||||
console.debug(summaryLog);
|
||||
|
||||
const context = getContext();
|
||||
if (saveToMessage && context.chat.length) {
|
||||
const idx = index ?? context.chat.length - 2;
|
||||
const mes = context.chat[idx < 0 ? 0 : idx];
|
||||
@ -894,6 +904,7 @@ function setupListeners() {
|
||||
$('#memory_prompt_words_auto').off('click').on('click', onPromptForceWordsAutoClick);
|
||||
$('#memory_override_response_length').off('click').on('input', onOverrideResponseLengthInput);
|
||||
$('#memory_max_messages_per_request').off('click').on('input', onMaxMessagesPerRequestInput);
|
||||
$('#memory_include_wi_scan').off('input').on('input', onMemoryIncludeWIScanInput);
|
||||
$('#summarySettingsBlockToggle').off('click').on('click', function () {
|
||||
console.log('saw settings button click');
|
||||
$('#summarySettingsBlock').slideToggle(200, 'swing'); //toggleClass("hidden");
|
||||
|
@ -109,7 +109,16 @@
|
||||
<textarea id="memory_template" class="text_pole textarea_compact" rows="2" data-i18n="[placeholder]ext_sum_memory_template_placeholder" placeholder="{{summary}} will resolve to the current summary contents."></textarea>
|
||||
</div>
|
||||
<label for="memory_position" data-i18n="ext_sum_injection_position">Injection Position</label>
|
||||
<label class="checkbox_label" for="memory_include_wi_scan" data-i18n="[title]ext_sum_include_wi_scan_desc" title="Include the latest summary in the WI scan.">
|
||||
<input id="memory_include_wi_scan" type="checkbox" />
|
||||
<span data-i18n="ext_sum_include_wi_scan">Include in World Info Scanning</span>
|
||||
</label>
|
||||
<div class="radio_group">
|
||||
<label>
|
||||
<input type="radio" name="memory_position" value="-1" />
|
||||
<span data-i18n="None (not injected)">None (not injected)</span>
|
||||
<i class="fa-solid fa-info-circle" title="The summary will not be injected into the prompt. You can still access it via the {{summary}} macro." data-i18n="[title]ext_sum_injection_position_none"></i>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="memory_position" value="2" />
|
||||
<span data-i18n="Before Main Prompt / Story String">Before Main Prompt / Story String</span>
|
||||
|
@ -335,8 +335,8 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
|
||||
min-height: 50svh;
|
||||
height: 50svh;
|
||||
min-height: 50dvh;
|
||||
height: 50dvh;
|
||||
}
|
||||
}
|
||||
.popup:has(#qr--modalEditor) {
|
||||
|
@ -395,8 +395,8 @@
|
||||
}
|
||||
|
||||
>#qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder {
|
||||
min-height: 50svh;
|
||||
height: 50svh;
|
||||
min-height: 50dvh;
|
||||
height: 50dvh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ const controls = [
|
||||
{ id: 'instruct_names_force_groups', property: 'names_force_groups', isCheckbox: true },
|
||||
{ id: 'instruct_first_output_sequence', property: 'first_output_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_last_output_sequence', property: 'last_output_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_first_input_sequence', property: 'first_input_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_last_input_sequence', property: 'last_input_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_activation_regex', property: 'activation_regex', isCheckbox: false },
|
||||
{ id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },
|
||||
{ id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },
|
||||
@ -58,6 +60,8 @@ function migrateInstructModeSettings(settings) {
|
||||
system_suffix: '',
|
||||
user_alignment_message: '',
|
||||
last_system_sequence: '',
|
||||
first_input_sequence: '',
|
||||
last_input_sequence: '',
|
||||
names_force_groups: true,
|
||||
skip_examples: false,
|
||||
system_same_as_user: false,
|
||||
@ -253,7 +257,15 @@ export function getInstructStoppingSequences() {
|
||||
const system_sequence = power_user.instruct.system_sequence?.replace(/{{name}}/gi, 'System') || '';
|
||||
const last_system_sequence = power_user.instruct.last_system_sequence?.replace(/{{name}}/gi, 'System') || '';
|
||||
|
||||
const combined_sequence = `${stop_sequence}\n${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}\n${system_sequence}\n${last_system_sequence}`;
|
||||
const combined_sequence = [
|
||||
stop_sequence,
|
||||
input_sequence,
|
||||
output_sequence,
|
||||
first_output_sequence,
|
||||
last_output_sequence,
|
||||
system_sequence,
|
||||
last_system_sequence,
|
||||
].join('\n');
|
||||
|
||||
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
|
||||
}
|
||||
@ -301,6 +313,14 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
|
||||
}
|
||||
|
||||
if (isUser) {
|
||||
if (forceOutputSequence === force_output_sequence.FIRST) {
|
||||
return power_user.instruct.first_input_sequence || power_user.instruct.input_sequence;
|
||||
}
|
||||
|
||||
if (forceOutputSequence === force_output_sequence.LAST) {
|
||||
return power_user.instruct.last_input_sequence || power_user.instruct.input_sequence;
|
||||
}
|
||||
|
||||
return power_user.instruct.input_sequence;
|
||||
}
|
||||
|
||||
@ -552,6 +572,8 @@ export function replaceInstructMacros(input, env) {
|
||||
'instructStop': power_user.instruct.stop_sequence,
|
||||
'instructUserFiller': power_user.instruct.user_alignment_message,
|
||||
'instructSystemInstructionPrefix': power_user.instruct.last_system_sequence,
|
||||
'instructFirstInput|instructFirstUserPrefix': power_user.instruct.first_input_sequence || power_user.instruct.input_sequence,
|
||||
'instructLastInput|instructLastUserPrefix': power_user.instruct.last_input_sequence || power_user.instruct.input_sequence,
|
||||
};
|
||||
|
||||
for (const [placeholder, value] of Object.entries(instructMacros)) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
callPopup,
|
||||
characters,
|
||||
chat,
|
||||
chat_metadata,
|
||||
@ -22,7 +21,7 @@ import { PAGINATION_TEMPLATE, debounce, delay, download, ensureImageFormatSuppor
|
||||
import { debounce_timeout } from './constants.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
import { selected_group } from './group-chats.js';
|
||||
import { POPUP_TYPE, Popup } from './popup.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
|
||||
|
||||
let savePersonasPage = 0;
|
||||
const GRID_STORAGE_KEY = 'Personas_GridView';
|
||||
@ -332,15 +331,14 @@ async function changeUserAvatar(e) {
|
||||
* @returns {Promise} Promise that resolves when the persona is set
|
||||
*/
|
||||
export async function createPersona(avatarId) {
|
||||
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>Cancel if you\'re just uploading an avatar.', 'input', '');
|
||||
const personaName = await Popup.show.input('Enter a name for this persona:', 'Cancel if you\'re just uploading an avatar.', '');
|
||||
|
||||
if (!personaName) {
|
||||
console.debug('User cancelled creating a persona');
|
||||
return;
|
||||
}
|
||||
|
||||
await delay(500);
|
||||
const personaDescription = await callPopup('<h3>Enter a description for this persona:</h3>You can always add or change it later.', 'input', '', { rows: 4 });
|
||||
const personaDescription = await Popup.show.input('Enter a description for this persona:', 'You can always add or change it later.', '', { rows: 4 });
|
||||
|
||||
initPersona(avatarId, personaName, personaDescription);
|
||||
if (power_user.persona_show_notifications) {
|
||||
@ -349,7 +347,7 @@ export async function createPersona(avatarId) {
|
||||
}
|
||||
|
||||
async function createDummyPersona() {
|
||||
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>', 'input', '');
|
||||
const personaName = await Popup.show.input('Enter a name for this persona:', null);
|
||||
|
||||
if (!personaName) {
|
||||
console.debug('User cancelled creating dummy persona');
|
||||
@ -508,15 +506,20 @@ async function bindUserNameToPersona(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
let personaUnbind = false;
|
||||
const existingPersona = power_user.personas[avatarId];
|
||||
const personaName = await callPopup('<h3>Enter a name for this persona:</h3>(If empty name is provided, this will unbind the name from this avatar)', 'input', existingPersona || '');
|
||||
const personaName = await Popup.show.input(
|
||||
'Enter a name for this persona:',
|
||||
'(If empty name is provided, this will unbind the name from this avatar)',
|
||||
existingPersona || '',
|
||||
{ onClose: (p) => { personaUnbind = p.value === '' && p.result === POPUP_RESULT.AFFIRMATIVE; } });
|
||||
|
||||
// If the user clicked cancel, don't do anything
|
||||
if (personaName === false) {
|
||||
if (personaName === null && !personaUnbind) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (personaName.length > 0) {
|
||||
if (personaName && personaName.length > 0) {
|
||||
// If the user clicked ok and entered a name, bind the name to the persona
|
||||
console.log(`Binding persona ${avatarId} to name ${personaName}`);
|
||||
power_user.personas[avatarId] = personaName;
|
||||
@ -643,7 +646,12 @@ async function lockPersona() {
|
||||
);
|
||||
}
|
||||
power_user.personas[user_avatar] = name1;
|
||||
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT };
|
||||
power_user.persona_descriptions[user_avatar] = {
|
||||
description: '',
|
||||
position: persona_description_positions.IN_PROMPT,
|
||||
depth: DEFAULT_DEPTH,
|
||||
role: DEFAULT_ROLE,
|
||||
};
|
||||
}
|
||||
|
||||
chat_metadata['persona'] = user_avatar;
|
||||
@ -672,7 +680,7 @@ async function deleteUserAvatar(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await callPopup('<h3>Are you sure you want to delete this avatar?</h3>All information associated with its linked persona will be lost.', 'confirm');
|
||||
const confirm = await Popup.show.confirm('Are you sure you want to delete this avatar?', 'All information associated with its linked persona will be lost.');
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled deleting avatar');
|
||||
@ -806,7 +814,7 @@ async function setDefaultPersona(e) {
|
||||
const personaName = power_user.personas[avatarId];
|
||||
|
||||
if (avatarId === currentDefault) {
|
||||
const confirm = await callPopup('Are you sure you want to remove the default persona?', 'confirm');
|
||||
const confirm = await Popup.show.confirm('Are you sure you want to remove the default persona?', personaName);
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled removing default persona');
|
||||
@ -819,8 +827,7 @@ async function setDefaultPersona(e) {
|
||||
}
|
||||
delete power_user.default_persona;
|
||||
} else {
|
||||
const confirm = await callPopup(`<h3>Are you sure you want to set "${personaName}" as the default persona?</h3>
|
||||
This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.`, 'confirm');
|
||||
const confirm = await Popup.show.confirm(`Are you sure you want to set "${personaName}" as the default persona?`, 'This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.');
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled setting default persona');
|
||||
@ -978,7 +985,7 @@ async function onPersonasRestoreInput(e) {
|
||||
}
|
||||
|
||||
async function syncUserNameToPersona() {
|
||||
const confirmation = await callPopup(`<h3>Are you sure?</h3>All user-sent messages in this chat will be attributed to ${name1}.`, 'confirm');
|
||||
const confirmation = await Popup.show.confirm('Are you sure?', `All user-sent messages in this chat will be attributed to ${name1}.`);
|
||||
|
||||
if (!confirmation) {
|
||||
return;
|
||||
@ -1001,6 +1008,42 @@ export function retriggerFirstMessageOnEmptyChat() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates a persona.
|
||||
* @param {string} avatarId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function duplicatePersona(avatarId) {
|
||||
const personaName = power_user.personas[avatarId];
|
||||
|
||||
if (!personaName) {
|
||||
toastr.warning('Chosen avatar is not a persona');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await Popup.show.confirm('Are you sure you want to duplicate this persona?', personaName);
|
||||
|
||||
if (!confirm) {
|
||||
console.debug('User cancelled duplicating persona');
|
||||
return;
|
||||
}
|
||||
|
||||
const newAvatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
|
||||
const descriptor = power_user.persona_descriptions[avatarId];
|
||||
|
||||
power_user.personas[newAvatarId] = personaName;
|
||||
power_user.persona_descriptions[newAvatarId] = {
|
||||
description: descriptor?.description ?? '',
|
||||
position: descriptor?.position ?? persona_description_positions.IN_PROMPT,
|
||||
depth: descriptor?.depth ?? DEFAULT_DEPTH,
|
||||
role: descriptor?.role ?? DEFAULT_ROLE,
|
||||
};
|
||||
|
||||
await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId);
|
||||
await getUserAvatars(true, newAvatarId);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
export function initPersonas() {
|
||||
$(document).on('click', '.bind_user_name', bindUserNameToPersona);
|
||||
$(document).on('click', '.set_default_persona', setDefaultPersona);
|
||||
@ -1059,6 +1102,18 @@ export function initPersonas() {
|
||||
$('#avatar_upload_file').trigger('click');
|
||||
});
|
||||
|
||||
$(document).on('click', '#user_avatar_block .duplicate_persona', function (e) {
|
||||
e.stopPropagation();
|
||||
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
|
||||
|
||||
if (!avatarId) {
|
||||
console.log('no imgfile');
|
||||
return;
|
||||
}
|
||||
|
||||
duplicatePersona(avatarId);
|
||||
});
|
||||
|
||||
$(document).on('click', '#user_avatar_block .set_persona_image', function (e) {
|
||||
e.stopPropagation();
|
||||
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
|
||||
|
@ -73,8 +73,8 @@ const showPopupHelper = {
|
||||
/**
|
||||
* Asynchronously displays an input popup with the given header and text, and returns the user's input.
|
||||
*
|
||||
* @param {string} header - The header text for the popup.
|
||||
* @param {string} text - The main text for the popup.
|
||||
* @param {string?} header - The header text for the popup.
|
||||
* @param {string?} text - The main text for the popup.
|
||||
* @param {string} [defaultValue=''] - The default value for the input field.
|
||||
* @param {PopupOptions} [popupOptions={}] - Options for the popup.
|
||||
* @return {Promise<string?>} A Promise that resolves with the user's input.
|
||||
@ -600,15 +600,15 @@ class PopupUtils {
|
||||
/**
|
||||
* Builds popup content with header and text below
|
||||
*
|
||||
* @param {string} header - The header to be added to the text
|
||||
* @param {string} text - The main text content
|
||||
* @param {string?} header - The header to be added to the text
|
||||
* @param {string?} text - The main text content
|
||||
*/
|
||||
static BuildTextWithHeader(header, text) {
|
||||
if (!header) {
|
||||
return text;
|
||||
}
|
||||
return `<h3>${header}</h3>
|
||||
${text}`;
|
||||
${text ?? ''}`; // Convert no text to empty string
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2180,7 +2180,7 @@ function validateStoryString(storyString, params) {
|
||||
validateMissingField('personality');
|
||||
validateMissingField('persona');
|
||||
validateMissingField('scenario');
|
||||
validateMissingField('system');
|
||||
// validateMissingField('system');
|
||||
validateMissingField('wiBefore', 'loreBefore');
|
||||
validateMissingField('wiAfter', 'loreAfter');
|
||||
|
||||
@ -2973,7 +2973,13 @@ function setAvgBG() {
|
||||
return '';
|
||||
}
|
||||
|
||||
async function setThemeCallback(_, text) {
|
||||
async function setThemeCallback(_, themeName) {
|
||||
if (!themeName) {
|
||||
// allow reporting of the theme name if called without args
|
||||
// for use in ST Scripts via pipe
|
||||
return power_user.theme;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const fuse = new Fuse(themes, {
|
||||
keys: [
|
||||
@ -2981,12 +2987,12 @@ async function setThemeCallback(_, text) {
|
||||
],
|
||||
});
|
||||
|
||||
const results = fuse.search(text);
|
||||
console.debug('Theme fuzzy search results for ' + text, results);
|
||||
const results = fuse.search(themeName);
|
||||
console.debug('Theme fuzzy search results for ' + themeName, results);
|
||||
const theme = results[0]?.item;
|
||||
|
||||
if (!theme) {
|
||||
toastr.warning(`Could not find theme with name: ${text}`);
|
||||
toastr.warning(`Could not find theme with name: ${themeName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3333,10 +3339,11 @@ $(document).ready(() => {
|
||||
|
||||
});
|
||||
|
||||
$('#chat_width_slider').on('input', function (e) {
|
||||
$('#chat_width_slider').on('input', function (e, data) {
|
||||
const applyMode = data?.forced ? 'forced' : 'normal';
|
||||
power_user.chat_width = Number(e.target.value);
|
||||
localStorage.setItem(storage_keys.chat_width, power_user.chat_width);
|
||||
applyChatWidth();
|
||||
applyChatWidth(applyMode);
|
||||
setHotswapsDebounced();
|
||||
});
|
||||
|
||||
@ -3362,11 +3369,12 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('input[name="font_scale"]').on('input', async function (e) {
|
||||
$('input[name="font_scale"]').on('input', async function (e, data) {
|
||||
const applyMode = data?.forced ? 'forced' : 'normal';
|
||||
power_user.font_scale = Number(e.target.value);
|
||||
$('#font_scale_counter').val(power_user.font_scale);
|
||||
localStorage.setItem(storage_keys.font_scale, power_user.font_scale);
|
||||
await applyFontScale();
|
||||
await applyFontScale(applyMode);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
@ -4065,13 +4073,30 @@ $(document).ready(() => {
|
||||
callback: setThemeCallback,
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'name',
|
||||
description: 'theme name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: () => themes.map(theme => new SlashCommandEnumValue(theme.name)),
|
||||
}),
|
||||
],
|
||||
helpString: 'sets a UI theme by name',
|
||||
helpString: `
|
||||
<div>
|
||||
Sets a UI theme by name.
|
||||
</div>
|
||||
<div>
|
||||
If no theme name is is provided, this will return the currently active theme.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code>/theme Cappuccino</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
<pre><code>/theme</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'movingui',
|
||||
|
@ -142,9 +142,8 @@ export function initDefaultSlashCommands() {
|
||||
returns: 'the current background',
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'filename',
|
||||
description: 'background filename',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: () => [...document.querySelectorAll('.bg_example')]
|
||||
.map(it => new SlashCommandEnumValue(it.getAttribute('bgfile')))
|
||||
.filter(it => it.value?.length),
|
||||
@ -154,12 +153,18 @@ export function initDefaultSlashCommands() {
|
||||
<div>
|
||||
Sets a background according to the provided filename. Partial names allowed.
|
||||
</div>
|
||||
<div>
|
||||
If no background is provided, this will return the currently selected background.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code>/bg beach.jpg</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
<pre><code>/bg</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
@ -1359,7 +1364,7 @@ export function initDefaultSlashCommands() {
|
||||
enumProvider: commonEnumProviders.injects,
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'position', 'injection position', [ARGUMENT_TYPE.STRING], false, false, 'after', ['before', 'after', 'chat'],
|
||||
'position', 'injection position', [ARGUMENT_TYPE.STRING], false, false, 'after', ['before', 'after', 'chat', 'none'],
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'depth', 'injection depth', [ARGUMENT_TYPE.NUMBER], false, false, '4',
|
||||
@ -1387,7 +1392,7 @@ export function initDefaultSlashCommands() {
|
||||
'text', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
],
|
||||
helpString: 'Injects a text into the LLM prompt for the current chat. Requires a unique injection ID. Positions: "before" main prompt, "after" main prompt, in-"chat" (default: after). Depth: injection depth for the prompt (default: 4). Role: role for in-chat injections (default: system). Scan: include injection content into World Info scans (default: false).',
|
||||
helpString: 'Injects a text into the LLM prompt for the current chat. Requires a unique injection ID. Positions: "before" main prompt, "after" main prompt, in-"chat", hidden with "none" (default: after). Depth: injection depth for the prompt (default: 4). Role: role for in-chat injections (default: system). Scan: include injection content into World Info scans (default: false). Hidden injects in "none" position are not inserted into the prompt but can be used for triggering WI entries.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'listinjects',
|
||||
@ -1504,6 +1509,7 @@ function injectCallback(args, value) {
|
||||
'before': extension_prompt_types.BEFORE_PROMPT,
|
||||
'after': extension_prompt_types.IN_PROMPT,
|
||||
'chat': extension_prompt_types.IN_CHAT,
|
||||
'none': extension_prompt_types.NONE,
|
||||
};
|
||||
const roles = {
|
||||
'system': extension_prompt_roles.SYSTEM,
|
||||
@ -2879,7 +2885,6 @@ export async function sendMessageAs(args, text) {
|
||||
|
||||
if (args.name) {
|
||||
name = args.name.trim();
|
||||
mesText = text.trim();
|
||||
|
||||
if (!name && !text) {
|
||||
toastr.warning('You must specify a name and text to send as');
|
||||
@ -2892,8 +2897,14 @@ export async function sendMessageAs(args, text) {
|
||||
localStorage.setItem(namelessWarningKey, 'true');
|
||||
}
|
||||
name = name2;
|
||||
if (!text) {
|
||||
toastr.warning('You must specify text to send as');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
mesText = text.trim();
|
||||
|
||||
// Requires a regex check after the slash command is pushed to output
|
||||
mesText = getRegexedString(mesText, regex_placement.SLASH_COMMAND, { characterOverride: name });
|
||||
|
||||
|
@ -72,6 +72,8 @@
|
||||
<li><tt>{{instructSystemInstructionPrefix}}</tt> – <span data-i18n="help_macros_56">instruct system instruction prefix</span></li>
|
||||
<li><tt>{{instructUserFiller}}</tt> – <span data-i18n="help_macros_57">instruct first user message filler</span></li>
|
||||
<li><tt>{{instructStop}}</tt> – <span data-i18n="help_macros_58">instruct stop sequence</span></li>
|
||||
<li><tt>{{instructFirstUserPrefix}}</tt> – <span data-i18n="help_macros_first_user">instruct user first input sequence</span></li>
|
||||
<li><tt>{{instructLastUserPrefix}}</tt> – <span data-i18n="help_macros_last_user">instruct user last input sequence</span></li>
|
||||
</ul>
|
||||
<div data-i18n="Chat variables Macros:">
|
||||
Chat variables Macros:
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div id="tabby_downloader_popup">
|
||||
<div>
|
||||
<h3><strong data-i18n="">Download Model</strong>
|
||||
<h3><strong data-i18n="Download Model">Download Model</strong>
|
||||
<a href="https://github.com/theroyallab/async-hf-downloader" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
@ -58,4 +58,4 @@
|
||||
<textarea class="text_pole textarea_compact" name="tabby_download_exclude" placeholder="Ex. *.txt"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,7 +31,7 @@
|
||||
<h3 data-i18n="Confused or lost?">Confused or lost?</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="note-link-span"><a class="fa-solid fa-circle-question" target="_blank" href="https://docs.sillytavern.app/" data-i18n=""></a></span> - <span data-i18n="click these icons!">click these icons!</span>
|
||||
<span class="note-link-span"><a class="fa-solid fa-circle-question" target="_blank" href="https://docs.sillytavern.app/"></a></span> - <span data-i18n="click these icons!">click these icons!</span>
|
||||
</li>
|
||||
<li>
|
||||
<span data-i18n="Enter">Enter </span><code>/?</code><span data-i18n="in the chat bar"> in the chat bar</span>
|
||||
|
@ -1184,7 +1184,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
||||
if (settings.type === HUGGINGFACE) {
|
||||
params.top_p = Math.min(Math.max(Number(params.top_p), 0.0), 0.999);
|
||||
params.stop = Array.isArray(params.stop) ? params.stop.slice(0, 4) : [];
|
||||
nonAphroditeParams.seed = settings.seed >= 0 ? settings.seed : undefined;
|
||||
nonAphroditeParams.seed = settings.seed >= 0 ? settings.seed : Math.floor(Math.random() * Math.pow(2, 32));
|
||||
}
|
||||
|
||||
if (settings.type === MANCER) {
|
||||
|
@ -704,6 +704,22 @@ export function isOdd(number) {
|
||||
return number % 2 !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two moment objects for sorting.
|
||||
* @param {moment.Moment} a The first moment object.
|
||||
* @param {moment.Moment} b The second moment object.
|
||||
* @returns {number} A negative number if a is before b, a positive number if a is after b, or 0 if they are equal.
|
||||
*/
|
||||
export function sortMoments(a, b) {
|
||||
if (a.isBefore(b)) {
|
||||
return 1;
|
||||
} else if (a.isAfter(b)) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const dateCache = new Map();
|
||||
|
||||
/**
|
||||
@ -717,26 +733,27 @@ export function timestampToMoment(timestamp) {
|
||||
return dateCache.get(timestamp);
|
||||
}
|
||||
|
||||
const moment = parseTimestamp(timestamp);
|
||||
dateCache.set(timestamp, moment);
|
||||
return moment;
|
||||
const iso8601 = parseTimestamp(timestamp);
|
||||
const objMoment = iso8601 ? moment(iso8601) : moment.invalid();
|
||||
|
||||
dateCache.set(timestamp, objMoment);
|
||||
return objMoment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a timestamp and returns a moment object representing the parsed date and time.
|
||||
* @param {string|number} timestamp - The timestamp to parse. It can be a string or a number.
|
||||
* @returns {moment.Moment} - A moment object representing the parsed date and time. If the timestamp is invalid, an invalid moment object is returned.
|
||||
* @returns {string} - If the timestamp is valid, returns an ISO 8601 string.
|
||||
*/
|
||||
function parseTimestamp(timestamp) {
|
||||
if (!timestamp) return moment.invalid();
|
||||
if (!timestamp) return;
|
||||
|
||||
// Unix time (legacy TAI / tags)
|
||||
if (typeof timestamp === 'number' || /^\d+$/.test(timestamp)) {
|
||||
const number = Number(timestamp);
|
||||
if (isNaN(number)) return moment.invalid();
|
||||
if (!isFinite(number)) return moment.invalid();
|
||||
if (number < 0) return moment.invalid();
|
||||
return moment(number);
|
||||
const unixTime = Number(timestamp);
|
||||
const isValid = Number.isFinite(unixTime) && !Number.isNaN(unixTime) && unixTime >= 0;
|
||||
if (!isValid) return;
|
||||
return new Date(unixTime).toISOString();
|
||||
}
|
||||
|
||||
let dtFmt = [];
|
||||
@ -760,32 +777,12 @@ function parseTimestamp(timestamp) {
|
||||
// 2024-6-5 @14h 56m 50s 682ms
|
||||
dtFmt.push({ callback: convertFromHumanized, pattern: /(\d{4})-(\d{1,2})-(\d{1,2}) @(\d{1,2})h (\d{1,2})m (\d{1,2})s (\d{1,3})ms/ });
|
||||
|
||||
let iso8601;
|
||||
for (const x of dtFmt) {
|
||||
let rgxMatch = timestamp.match(x.pattern);
|
||||
if (!rgxMatch) continue;
|
||||
iso8601 = x.callback(...rgxMatch);
|
||||
break;
|
||||
}
|
||||
|
||||
// If one of the patterns matched, return a valid moment object, otherwise return an invalid moment object
|
||||
return iso8601 ? moment(iso8601) : moment.invalid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two moment objects for sorting.
|
||||
* @param {moment.Moment} a The first moment object.
|
||||
* @param {moment.Moment} b The second moment object.
|
||||
* @returns {number} A negative number if a is before b, a positive number if a is after b, or 0 if they are equal.
|
||||
*/
|
||||
export function sortMoments(a, b) {
|
||||
if (a.isBefore(b)) {
|
||||
return 1;
|
||||
} else if (a.isAfter(b)) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
return x.callback(...rgxMatch);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/** Split string to parts no more than length in size.
|
||||
|
@ -893,7 +893,7 @@ function registerWorldInfoSlashCommands() {
|
||||
* @param {boolean} [loadIfNotSelected=false] - Indicates whether to load the file even if it's not currently selected
|
||||
*/
|
||||
function reloadEditor(file, loadIfNotSelected = false) {
|
||||
const currentIndex = $('#world_editor_select').val();
|
||||
const currentIndex = Number($('#world_editor_select').val());
|
||||
const selectedIndex = world_names.indexOf(file);
|
||||
if (selectedIndex !== -1 && (loadIfNotSelected || currentIndex === selectedIndex)) {
|
||||
$('#world_editor_select').val(selectedIndex).trigger('change');
|
||||
@ -1647,32 +1647,38 @@ function sortEntries(data) {
|
||||
|
||||
if (!data.length) return data;
|
||||
|
||||
/** @type {(a: any, b: any) => number} */
|
||||
let primarySort;
|
||||
|
||||
// Secondary and tertiary it will always be sorted by Order descending, and last UID ascending
|
||||
// This is the most sensible approach for sorts where the primary sort has a lot of equal values
|
||||
const secondarySort = (a, b) => b.order - a.order;
|
||||
const tertiarySort = (a, b) => a.uid - b.uid;
|
||||
|
||||
// If we have a search term for WI, we are sorting by weighting scores
|
||||
if (sortRule === 'search') {
|
||||
data.sort((a, b) => {
|
||||
primarySort = (a, b) => {
|
||||
const aScore = worldInfoFilter.getScore(FILTER_TYPES.WORLD_INFO_SEARCH, a.uid);
|
||||
const bScore = worldInfoFilter.getScore(FILTER_TYPES.WORLD_INFO_SEARCH, b.uid);
|
||||
return (aScore - bScore);
|
||||
});
|
||||
return aScore - bScore;
|
||||
};
|
||||
}
|
||||
else if (sortRule === 'custom') {
|
||||
// First by display index, then by order, then by uid
|
||||
data.sort((a, b) => {
|
||||
// First by display index
|
||||
primarySort = (a, b) => {
|
||||
const aValue = a.displayIndex;
|
||||
const bValue = b.displayIndex;
|
||||
|
||||
return (aValue - bValue || b.order - a.order || a.uid - b.uid);
|
||||
});
|
||||
return aValue - bValue;
|
||||
};
|
||||
} else if (sortRule === 'priority') {
|
||||
// First constant, then normal, then disabled. Then sort by order
|
||||
data.sort((a, b) => {
|
||||
// First constant, then normal, then disabled.
|
||||
primarySort = (a, b) => {
|
||||
const aValue = a.constant ? 0 : a.disable ? 2 : 1;
|
||||
const bValue = b.constant ? 0 : b.disable ? 2 : 1;
|
||||
|
||||
return (aValue - bValue || b.order - a.order);
|
||||
});
|
||||
return aValue - bValue;
|
||||
};
|
||||
} else {
|
||||
const primarySort = (a, b) => {
|
||||
primarySort = (a, b) => {
|
||||
const aValue = a[sortField];
|
||||
const bValue = b[sortField];
|
||||
|
||||
@ -1690,26 +1696,12 @@ function sortEntries(data) {
|
||||
// Sort numbers
|
||||
return orderSign * (Number(aValue) - Number(bValue));
|
||||
};
|
||||
const secondarySort = (a, b) => a.order - b.order;
|
||||
const tertiarySort = (a, b) => a.uid - b.uid;
|
||||
|
||||
data.sort((a, b) => {
|
||||
const primary = primarySort(a, b);
|
||||
|
||||
if (primary !== 0) {
|
||||
return primary;
|
||||
}
|
||||
|
||||
const secondary = secondarySort(a, b);
|
||||
|
||||
if (secondary !== 0) {
|
||||
return secondary;
|
||||
}
|
||||
|
||||
return tertiarySort(a, b);
|
||||
});
|
||||
}
|
||||
|
||||
data.sort((a, b) => {
|
||||
return primarySort(a, b) || secondarySort(a, b) || tertiarySort(a, b);
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -1939,39 +1931,43 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
|
||||
}
|
||||
});
|
||||
|
||||
$('#world_apply_custom_sorting').off('click').on('click', async () => {
|
||||
$('#world_apply_current_sorting').off('click').on('click', async () => {
|
||||
const entryCount = Object.keys(data.entries).length;
|
||||
const moreThan100 = entryCount > 100;
|
||||
|
||||
let content = '<span>Apply your custom sorting to the "Order" field. The Order values will go down from the chosen number.</span>';
|
||||
let content = '<span>Apply your current sorting to the "Order" field. The Order values will go down from the chosen number.</span>';
|
||||
if (moreThan100) {
|
||||
content += `<div class="m-t-1"><i class="fa-solid fa-triangle-exclamation" style="color: #FFD43B;"></i> More than 100 entries in this world. If you don't choose a number higher than that, the lower entries will default to 0.<br />(Usual default: 100)<br />Minimum: ${entryCount}</div>`;
|
||||
}
|
||||
|
||||
const result = await Popup.show.input('Apply Custom Sorting', content, '100', { okButton: 'Apply', cancelButton: 'Cancel' });
|
||||
const result = await Popup.show.input('Apply Current Sorting', content, '100', { okButton: 'Apply', cancelButton: 'Cancel' });
|
||||
if (!result) return;
|
||||
|
||||
const start = Number(result);
|
||||
if (isNaN(start) || start < 0) {
|
||||
toastr.error('Invalid number: ' + result, 'Apply Custom Sorting');
|
||||
toastr.error('Invalid number: ' + result, 'Apply Current Sorting');
|
||||
return;
|
||||
}
|
||||
if (start < entryCount) {
|
||||
toastr.warning('A number lower than the entry count has been chosen. All entries below that will default to 0.', 'Apply Custom Sorting');
|
||||
toastr.warning('A number lower than the entry count has been chosen. All entries below that will default to 0.', 'Apply Current Sorting');
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
for (const entry of Object.values(data.entries)) {
|
||||
const newOrder = Math.max(start - (entry.displayIndex ?? 0), 0);
|
||||
// We need to sort the entries here, as the data source isn't sorted
|
||||
const entries = Object.values(data.entries);
|
||||
sortEntries(entries);
|
||||
|
||||
let updated = 0, current = start;
|
||||
for (const entry of entries) {
|
||||
const newOrder = Math.max(current--, 0);
|
||||
if (entry.order === newOrder) continue;
|
||||
|
||||
entry.order = newOrder;
|
||||
setOriginalDataValue(data, entry.order, 'order', entry.order);
|
||||
counter++;
|
||||
updated++;
|
||||
}
|
||||
|
||||
if (counter > 0) {
|
||||
toastr.info(`Updated ${counter} Order values`, 'Apply Custom Sorting');
|
||||
if (updated > 0) {
|
||||
toastr.info(`Updated ${updated} Order values`, 'Apply Custom Sorting');
|
||||
await saveWorldInfo(name, data, true);
|
||||
updateEditor(navigation_option.previous);
|
||||
} else {
|
||||
|
@ -137,7 +137,6 @@ body {
|
||||
width: 100%;
|
||||
/*fallback for JS load*/
|
||||
height: 100vh;
|
||||
height: 100svh;
|
||||
height: 100dvh;
|
||||
/*defaults as 100%, then reassigned via JS as pixels, will work on PC and Android*/
|
||||
/*height: calc(var(--doc-height) - 1px);*/
|
||||
@ -451,7 +450,7 @@ code {
|
||||
border-radius: 5px;
|
||||
background-color: var(--black70a);
|
||||
padding: 0 3px;
|
||||
/* max-width: calc(100svw - 95px); */
|
||||
/* max-width: calc(100dvw - 95px); */
|
||||
line-height: var(--mainFontSize);
|
||||
color: var(--white70a);
|
||||
}
|
||||
@ -535,13 +534,13 @@ body.reduced-motion #bg_custom {
|
||||
flex-direction: column;
|
||||
/* -1px to give sheld some wiggle room to bounce off tobar when moving*/
|
||||
height: calc(100vh - var(--topBarBlockSize) - 1px);
|
||||
height: calc(100svh - var(--topBarBlockSize) - 1px);
|
||||
max-height: calc(100svh - var(--topBarBlockSize) - 1px);
|
||||
height: calc(100dvh - var(--topBarBlockSize) - 1px);
|
||||
max-height: calc(100dvh - var(--topBarBlockSize) - 1px);
|
||||
overflow-x: hidden;
|
||||
/* max-width: 50vw; */
|
||||
position: absolute;
|
||||
left: calc((100vw - var(--sheldWidth))/2);
|
||||
left: calc((100svw - var(--sheldWidth))/2);
|
||||
left: calc((100dvw - var(--sheldWidth))/2);
|
||||
top: var(--topBarBlockSize);
|
||||
margin: 0 auto;
|
||||
left: 0;
|
||||
@ -1160,12 +1159,12 @@ textarea {
|
||||
font-family: var(--mainFontFamily);
|
||||
padding: 5px 10px;
|
||||
max-height: 90vh;
|
||||
max-height: 90svh;
|
||||
max-height: 90dvh;
|
||||
}
|
||||
|
||||
textarea.autoSetHeight {
|
||||
max-height: 50vh;
|
||||
max-height: 50svh;
|
||||
max-height: 50dvh;
|
||||
}
|
||||
|
||||
input,
|
||||
@ -1179,7 +1178,7 @@ select {
|
||||
min-height: calc(var(--bottomFormBlockSize) + 2px);
|
||||
height: calc(var(--bottomFormBlockSize) + 2px);
|
||||
max-height: 50vh;
|
||||
max-height: 50svh;
|
||||
max-height: 50dvh;
|
||||
word-wrap: break-word;
|
||||
resize: vertical;
|
||||
display: block;
|
||||
@ -2159,14 +2158,14 @@ textarea::placeholder {
|
||||
@media screen and (max-width: 1000px) {
|
||||
#form_create textarea {
|
||||
flex-grow: 1;
|
||||
min-height: 20svh;
|
||||
min-height: 20dvh;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1001px) {
|
||||
#description_textarea {
|
||||
height: 29vh;
|
||||
height: 29svh;
|
||||
height: 29dvh;
|
||||
}
|
||||
|
||||
#firstmessage_textarea {
|
||||
@ -2442,8 +2441,8 @@ input[type="file"] {
|
||||
#floatingPrompt,
|
||||
#cfgConfig {
|
||||
overflow-y: auto;
|
||||
max-width: 90svw;
|
||||
max-height: 90svh;
|
||||
max-width: 90dvw;
|
||||
max-height: 90dvh;
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
border-radius: 10px;
|
||||
@ -2459,7 +2458,7 @@ input[type="file"] {
|
||||
top: 0;
|
||||
margin: 0;
|
||||
right: unset;
|
||||
width: calc(((100svw - var(--sheldWidth)) / 2) - 1px);
|
||||
width: calc(((100dvw - var(--sheldWidth)) / 2) - 1px);
|
||||
|
||||
}
|
||||
|
||||
@ -2816,7 +2815,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
flex-wrap: wrap;
|
||||
width: calc(var(--sheldWidth) - 10px);
|
||||
max-width: 100vw;
|
||||
max-width: 100svw;
|
||||
max-width: 100dvw;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
@ -3080,6 +3079,10 @@ grammarly-extension {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.avatar-container .avatar-buttons .menu_button {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
/* Ross should be able to handle this later */
|
||||
/*.big-avatars .avatar-buttons{
|
||||
justify-content: center;
|
||||
@ -3171,7 +3174,7 @@ grammarly-extension {
|
||||
#dialogue_popup {
|
||||
width: 500px;
|
||||
max-width: 90vw;
|
||||
max-width: 90svw;
|
||||
max-width: 90dvw;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
margin-left: auto;
|
||||
@ -3187,7 +3190,7 @@ grammarly-extension {
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border-radius: 10px;
|
||||
max-height: 90vh;
|
||||
max-height: 90svh;
|
||||
max-height: 90dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: hidden;
|
||||
@ -3201,9 +3204,9 @@ grammarly-extension {
|
||||
|
||||
.large_dialogue_popup {
|
||||
height: 90vh !important;
|
||||
height: 90svh !important;
|
||||
height: 90dvh !important;
|
||||
max-width: 90vw !important;
|
||||
max-width: 90svw !important;
|
||||
max-width: 90dvw !important;
|
||||
}
|
||||
|
||||
.wide_dialogue_popup {
|
||||
@ -3360,7 +3363,7 @@ grammarly-extension {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100svh;
|
||||
height: 100dvh;
|
||||
z-index: 9999;
|
||||
top: 0;
|
||||
}
|
||||
@ -3368,9 +3371,9 @@ grammarly-extension {
|
||||
#bgtest {
|
||||
display: none;
|
||||
width: 100vw;
|
||||
width: 100svw;
|
||||
width: 100dvw;
|
||||
height: 100vh;
|
||||
height: 100svh;
|
||||
height: 100dvh;
|
||||
position: absolute;
|
||||
z-index: -100;
|
||||
background-color: red;
|
||||
@ -4007,7 +4010,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100svh;
|
||||
height: 100dvh;
|
||||
z-index: 2058;
|
||||
}
|
||||
|
||||
@ -4020,11 +4023,11 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
min-width: 100px;
|
||||
max-width: var(--sheldWidth);
|
||||
height: calc(100vh - 84px);
|
||||
height: calc(100svh - 84px);
|
||||
height: calc(100dvh - 84px);
|
||||
min-height: calc(100vh - 84px);
|
||||
min-height: calc(100svh - 84px);
|
||||
min-height: calc(100dvh - 84px);
|
||||
max-height: calc(100vh - 84px);
|
||||
max-height: calc(100svh - 84px);
|
||||
max-height: calc(100dvh - 84px);
|
||||
position: absolute;
|
||||
z-index: 4001;
|
||||
margin-left: auto;
|
||||
@ -4103,7 +4106,7 @@ h5 {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100svh;
|
||||
height: 100dvh;
|
||||
z-index: 4100;
|
||||
top: 0;
|
||||
background-color: var(--black70a);
|
||||
@ -4117,7 +4120,7 @@ h5 {
|
||||
max-width: var(--sheldWidth);
|
||||
height: min-content;
|
||||
max-height: calc(100vh - var(--topBarBlockSize));
|
||||
max-height: calc(100svh - var(--topBarBlockSize));
|
||||
max-height: calc(100dvh - var(--topBarBlockSize));
|
||||
min-height: 100px;
|
||||
position: absolute;
|
||||
z-index: 2066;
|
||||
@ -4437,14 +4440,14 @@ a {
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
max-width: calc(((100vw - 500px) / 2) - 10px);
|
||||
max-width: calc(((100svw - 500px) / 2) - 10px);
|
||||
max-width: calc(((100dvw - 500px) / 2) - 10px);
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
max-height: 90vh;
|
||||
max-height: 90svh;
|
||||
max-height: 90dvh;
|
||||
/*unsure why, but this prevents scrollbars*/
|
||||
height: 49vh;
|
||||
height: 49svh;
|
||||
height: 49dvh;
|
||||
|
||||
padding: 5px;
|
||||
overflow-y: auto;
|
||||
@ -4480,11 +4483,11 @@ a {
|
||||
|
||||
#right-nav-panel {
|
||||
width: calc((100vw - var(--sheldWidth) - 2px) /2);
|
||||
width: calc((100svw - var(--sheldWidth) - 2px) /2);
|
||||
width: calc((100dvw - var(--sheldWidth) - 2px) /2);
|
||||
max-height: calc(100vh - var(--topBarBlockSize));
|
||||
max-height: calc(100svh - var(--topBarBlockSize));
|
||||
max-height: calc(100dvh - var(--topBarBlockSize));
|
||||
height: calc(100vh - var(--topBarBlockSize));
|
||||
height: calc(100svh - var(--topBarBlockSize));
|
||||
height: calc(100dvh - var(--topBarBlockSize));
|
||||
position: fixed;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
@ -4537,7 +4540,7 @@ a {
|
||||
border-radius: 10px;
|
||||
max-width: 100%;
|
||||
max-height: 40vh;
|
||||
max-height: 40svh;
|
||||
max-height: 40dvh;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
|
||||
@ -4629,18 +4632,18 @@ body:not(.caption) .mes_img_caption {
|
||||
|
||||
.img_enlarged_container pre {
|
||||
max-height: 25vh;
|
||||
max-height: 25svh;
|
||||
max-height: 25dvh;
|
||||
flex-shrink: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.popup:has(.img_enlarged.zoomed).large_dialogue_popup {
|
||||
height: 100vh !important;
|
||||
height: 100svh !important;
|
||||
height: 100dvh !important;
|
||||
max-height: 100vh !important;
|
||||
max-height: 100svh !important;
|
||||
max-height: 100dvh !important;
|
||||
max-width: 100vw !important;
|
||||
max-width: 100svw !important;
|
||||
max-width: 100dvw !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@ -4827,7 +4830,7 @@ body:has(#character_popup.open) #top-settings-holder:has(.drawer-content.openDra
|
||||
width: var(--sheldWidth);
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
|
||||
max-height: calc(100svh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
|
||||
max-height: calc(100dvh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: var(--topBarBlockSize);
|
||||
@ -4862,11 +4865,11 @@ body:not(.movingUI) .drawer-content.maximized {
|
||||
|
||||
.fillLeft {
|
||||
width: calc((100vw - var(--sheldWidth) - 2px) /2);
|
||||
width: calc((100svw - var(--sheldWidth) - 2px) /2);
|
||||
width: calc((100dvw - var(--sheldWidth) - 2px) /2);
|
||||
height: calc(100vh - var(--topBarBlockSize));
|
||||
height: calc(100svh - var(--topBarBlockSize));
|
||||
height: calc(100dvh - var(--topBarBlockSize));
|
||||
max-height: calc(100vh - var(--topBarBlockSize));
|
||||
max-height: calc(100svh - var(--topBarBlockSize));
|
||||
max-height: calc(100dvh - var(--topBarBlockSize));
|
||||
position: fixed;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
@ -5116,7 +5119,7 @@ body:not(.movingUI) .drawer-content.maximized {
|
||||
width: 100%;
|
||||
/* margin-inline: 10px; */
|
||||
max-height: 90vh;
|
||||
max-width: 90svh;
|
||||
max-width: 90dvh;
|
||||
}
|
||||
|
||||
.zoomed_avatar img {
|
||||
|
@ -1,4 +1,5 @@
|
||||
require('./polyfill.js');
|
||||
const { getConfigValue } = require('./util.js');
|
||||
|
||||
/**
|
||||
* Convert a prompt from the ChatML objects to the format used by Claude.
|
||||
@ -373,8 +374,9 @@ function convertMistralMessages(messages, charName = '', userName = '') {
|
||||
}
|
||||
|
||||
// Make the last assistant message a prefill
|
||||
const prefixEnabled = getConfigValue('mistral.enablePrefix', false);
|
||||
const lastMsg = messages[messages.length - 1];
|
||||
if (messages.length > 0 && lastMsg && (lastMsg.role === 'assistant')) {
|
||||
if (prefixEnabled && messages.length > 0 && lastMsg?.role === 'assistant') {
|
||||
lastMsg.prefix = true;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user