diff --git a/.gitignore b/.gitignore index 458054342..8ac21adf0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,12 +6,14 @@ public/backgrounds/ public/groups/ public/group chats/ public/worlds/ +public/user/ public/css/bg_load.css public/themes/ public/OpenAI Settings/ public/KoboldAI Settings/ public/NovelAI Settings/ public/TextGen Settings/ +public/instruct/ public/scripts/extensions/third-party/ public/stats.json /uploads/ diff --git a/default/settings.json b/default/settings.json index e75af0e81..ad88f1088 100644 --- a/default/settings.json +++ b/default/settings.json @@ -133,7 +133,7 @@ "output_sequence": "### Response:", "preset": "Alpaca", "separator_sequence": "", - "macro": false + "macro": true }, "personas": {}, "default_persona": null, diff --git a/public/context/Default.json b/public/context/Default.json index de41e50de..e08277795 100644 --- a/public/context/Default.json +++ b/public/context/Default.json @@ -1,6 +1,6 @@ { "name": "Default", - "story_string": "{{#if description}}{{description}}{{/if}}\n{{#if personality}}{{personality}}{{/if}}\n{{#if scenario}}Scenario: {{scenario}}{{/if}}", + "story_string": "{{#if system}}{{system}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}", "chat_start": "***", "example_separator": "***" } diff --git a/public/context/Pygmalion.json b/public/context/Pygmalion.json index 46ff0a09a..c2c4cefae 100644 --- a/public/context/Pygmalion.json +++ b/public/context/Pygmalion.json @@ -1,6 +1,6 @@ { "name": "Pygmalion", - "story_string": "{{#if description}}{{{char}}}'s Persona: {{description}}{{/if}}\n{{#if personality}}Personality: {{personality}}{{/if}}\n{{#if scenario}}Scenario: {{scenario}}{{/if}}", + "story_string": "{{#if system}}{{system}}\n{{/if}}{{#if description}}{{{char}}}'s Persona: {{description}}\n{{/if}}{{#if personality}}Personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}", "chat_start": "", "example_separator": "" } diff --git a/public/context/Roleplay.json b/public/context/Roleplay.json new file mode 100644 index 000000000..89c2ea94f --- /dev/null +++ b/public/context/Roleplay.json @@ -0,0 +1,6 @@ +{ + "name": "Roleplay", + "story_string": "{{#if system}}{{system}}\n{{/if}}### Input:\n{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}", + "chat_start": "### New Roleplay:", + "example_separator": "### New Roleplay:" +} diff --git a/public/context/simple-proxy-for-tavern.json b/public/context/simple-proxy-for-tavern.json new file mode 100644 index 000000000..02f6efcc8 --- /dev/null +++ b/public/context/simple-proxy-for-tavern.json @@ -0,0 +1,6 @@ +{ + "name": "simple-proxy-for-tavern", + "story_string": "## {{char}}\n- You're \"{{char}}\" in this never-ending roleplay with \"{{user}}\".\n### Input:\n{{#if system}}{{system}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}### Response:\n(OOC) Understood. I will take this info into account for the roleplay. (end OOC)", + "chat_start": "### New Roleplay:", + "example_separator": "### New Roleplay:" +} diff --git a/public/css/extensions-panel.css b/public/css/extensions-panel.css new file mode 100644 index 000000000..1179292b2 --- /dev/null +++ b/public/css/extensions-panel.css @@ -0,0 +1,96 @@ +/* Extensions */ +#extensions_url { + display: block; +} + +#extensions_status { + /* margin-bottom: 10px; */ + font-weight: 700; +} + +.extensions_block input[type="submit"]:hover { + background-color: green; +} + +.extensions_block input[type="checkbox"] { + margin-left: 10px; + margin-right: 10px; +} + +label[for="extensions_autoconnect"] { + display: flex; + align-items: center; + margin: 0 !important; +} + +.extensions_url_block { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.extensions_url_block h4 { + display: inline; +} + +.extensions_block { + clear: both; + padding: 0.05px; +} + +.extensions_info { + text-align: left; + margin: 0 1em; +} + +.extensions_info h3 { + margin-bottom: 0.5em; +} + +.extensions_info h4 { + margin-bottom: 0.5em; +} + +.extensions_info p { + margin-bottom: 0.5em; + margin-left: 1em; +} + +.extensions_info .disabled { + color: lightgray; +} + +.extensions_info .toggle_enable { + color: lightgreen; +} + +.extensions_info .toggle_disable { + color: rgb(238, 144, 144); +} + +.extensions_info .extension_enabled { + color: green; +} + +.extensions_info .extension_disabled { + color: lightgray; +} + +.extensions_info .extension_missing { + color: gray; +} + +input.extension_missing[type="checkbox"] { + opacity: 0.5; +} + +#extensions_list .disabled { + text-decoration: line-through; + color: lightgray; +} + +.update-button { + margin-right: 10px; + display: inline-flex; +} \ No newline at end of file diff --git a/public/css/group-avatars.css b/public/css/group-avatars.css new file mode 100644 index 000000000..96f01e66e --- /dev/null +++ b/public/css/group-avatars.css @@ -0,0 +1,91 @@ +.avatar_collage { + border-radius: 50%; + position: relative; + overflow: hidden; +} + +.avatar_collage img { + position: absolute; + overflow: hidden; +} + +.collage_2 .img_1 { + left: 0; + top: 0; + max-width: 50%; + max-height: 100%; + width: 50%; + height: 100%; +} + +.collage_2 .img_2 { + left: 50%; + top: 0; + max-width: 50%; + max-height: 100%; + width: 50%; + height: 100%; +} + +.collage_3 .img_1 { + left: 0; + top: 0; + max-width: 50%; + max-height: 50%; + width: 50%; + height: 50%; +} + +.collage_3 .img_2 { + left: 50%; + top: 0; + max-width: 50%; + max-height: 50%; + width: 50%; + height: 50%; +} + +.collage_3 .img_3 { + left: 0; + top: 50%; + max-width: 100%; + max-height: 50%; + width: 100%; + height: 50%; +} + +.collage_4 .img_1 { + left: 0; + top: 0; + max-width: 50%; + max-height: 50%; + width: 50%; + height: 50%; +} + +.collage_4 .img_2 { + left: 50%; + top: 0; + max-width: 50%; + max-height: 50%; + width: 50%; + height: 50%; +} + +.collage_4 .img_3 { + left: 0; + top: 50%; + max-width: 50%; + max-height: 50%; + width: 50%; + height: 50%; +} + +.collage_4 .img_4 { + left: 50%; + top: 50%; + max-width: 50%; + max-height: 50%; + width: 50%; + height: 50%; +} \ No newline at end of file diff --git a/public/css/mobile-styles.css b/public/css/mobile-styles.css new file mode 100644 index 000000000..1db0d9426 --- /dev/null +++ b/public/css/mobile-styles.css @@ -0,0 +1,419 @@ +/*will apply to anything 1000px or less. this catches ipads, horizontal phones, and vertical phones)*/ +@media screen and (max-width: 1000px) { + + .bg_button { + font-size: 15px; + } + + .mes_text img { + width: 100%; + } + + #extensions_settings, + #extensions_settings2 { + width: 100% !important; + min-width: 100% !important; + } + + body:not(.waifuMode) .zoomed_avatar { + min-width: 100px; + min-height: 100px; + position: absolute; + padding: 0; + filter: drop-shadow(2px 2px 2px #51515199); + z-index: 30; + overflow: hidden; + left: 0; + right: 0; + margin: 0 auto; + top: 50px; + aspect-ratio: 2 / 3; + width: fit-content; + max-height: calc(60vh - 60px); + max-height: calc(60svh - 60px); + max-width: 90vw; + max-width: 90svw; + } + + .world_entry_thin_controls, + #persona-management-block, + #character_popup .flex-container { + flex-direction: column; + } + + #WIMultiSelector { + align-self: normal; + } + + .WIEntryContentAndMemo { + flex-flow: column; + } + + .WIEntryContentAndMemo .world_entry_thin_controls { + width: 100%; + } + + .world_entry_form_control.world_entry_form_horizontal { + /* flex-direction: column; */ + align-items: flex-start; + row-gap: 0.5rem; + } + + .world_entry_form_control.world_entry_form_horizontal .world_popup_expander { + display: none; + } + + /* #world_popup_header { + flex-direction: column; + align-items: flex-start; + } */ + + #world_popup_header .world_popup_expander { + display: none; + } + + body { + touch-action: none; + overflow: hidden; + position: fixed; + + } + + .drawer-content { + min-width: unset; + width: 100%; + max-height: calc(100vh - 45px); + max-height: calc(100svh - 45px); + position: fixed; + left: 0; + top: 5px; + border: 1px solid var(--grey30); + } + + #select_chat_popup { + align-items: start; + height: min-content; + align-content: start; + max-width: unset; + } + + #top-settings-holder, + #top-bar { + position: fixed; + padding-top: 8px; + width: 100vw; + width: 100svw; + } + + #bg1, + #bg_custom { + height: 100vh !important; + height: 100svh !important; + width: 100vw !important; + width: 100svw !important; + background-position: center; + + } + + + #sheld, + #character_popup, + .drawer-content + + /* , + #world_popup */ + { + max-height: calc(100vh - 40px); + max-height: calc(100svh - 40px); + width: 100% !important; + margin: 0 auto; + max-width: 100%; + left: 0 !important; + resize: none !important; + top: 40px; + } + + .wi-settings { + flex-direction: column; + } + + #character_popup, + #world_popup { + overflow-y: auto; + } + + #character_popup, + #send_form { + border: 1px solid var(--grey30); + backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); + max-width: 100dvw; + } + + #chat { + border-left: 1px solid var(--grey30); + border-right: 1px solid var(--grey30); + border-bottom: 1px solid var(--grey30); + align-items: start; + align-content: start; + overflow-y: auto; + overflow-x: hidden + } + + .mes_buttons { + font-size: calc(var(--mainFontSize)*1.2); + } + + .drag-grabber, + .pull-tab { + display: none !important; + + } + + #showRawPrompt, + #groupCurrentMemberPopoutButton { + display: none; + } + + #right-nav-panel, + #left-nav-panel, + #floatingPrompt, + #cfgConfig { + height: calc(100vh - 45px); + height: calc(100svh - 45px); + min-width: 100% !important; + width: 100% !important; + max-width: 100% !important; + overflow-y: hidden; + border-left: 1px solid var(--grey30); + border-right: 1px solid var(--grey30); + border-bottom: 1px solid var(--grey30); + border-radius: 0 0 20px 20px; + top: 40px !important; + left: 0 !important; + backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); + } + + #floatingPrompt, + #cfgConfig { + height: min-content; + } + + #right-nav-panel h4 { + margin: 5px auto; + } + + #result_info { + font-size: calc(var(--mainFontSize) - .1rem); + } + + /* .avatar_div { + margin-top: 5px; + } */ + + #character_popup { + width: 100%; + border-radius: 0 0 20px 20px; + margin-top: 0px; + height: calc(100% - 40px); + } + + .drawer25pWidth { + flex-basis: max(calc(100% / 4 - 10px), 190px); + } + + .drawer33pWidth { + flex-basis: max(calc(100% / 3 - 10px), 190px); + } + + .expression-holder { + display: none; + } + + body.waifuMode #sheld { + height: 40vh; + height: 40svh; + top: 60vh; + top: 60svh; + bottom: 0 !important; + } + + body.waifuMode .expression-holder { + /*display: inline;*/ + + max-width: 100vw; + height: 100vh; + width: max-content; + margin: 0 auto; + position: absolute; + left: 0; + right: 0; + filter: drop-shadow(2px 2px 2px #51515199); + z-index: 1; + } + + body.waifuMode img.expression { + object-fit: cover; + } + + body.waifuMode .zoomed_avatar { + width: fit-content; + max-height: calc(60vh - 60px); + max-height: calc(60svh - 60px); + max-width: 90vw; + max-width: 90svw; + } + + .scrollableInner { + overflow-y: auto; + overflow-x: hidden; + max-height: calc(100vh - 90px); + max-height: calc(100svh - 90px); + } + + .horde_multiple_hint { + display: none; + } + + #bg_menu_content { + width: unset; + } +} + +/*landscape mode phones and ipads*/ +@media screen and (max-width: 1000px) and (orientation: landscape) { + body.waifuMode img.expression { + object-fit: contain; + } + + .tag.excluded:after { + top: unset; + bottom: unset; + } + + body:not(.waifuMode) .zoomed_avatar { + + width: fit-content; + max-height: calc(60vh - 60px); + max-height: calc(60svh - 60px); + max-width: 90vw; + max-width: 90svw; + } + + +} + +/*portrait mode phones*/ +@media screen and (max-width: 450px) { + + body:not(.waifuMode) .zoomed_avatar { + min-width: 100px; + min-height: 100px; + max-height: 50vh; + max-width: 50vh; + width: 50vw; + position: absolute; + padding: 0; + filter: drop-shadow(2px 2px 2px #51515199); + z-index: 30; + overflow: hidden; + display: none; + left: 0; + right: 0; + margin: 0 auto; + top: 50px; + aspect-ratio: 2 / 3; + } + + .drawer25pWidth { + flex-basis: max(calc(100% / 2 - 10px), 180px); + } + + .drawer33pWidth { + flex-basis: max(calc(100% / 2 - 10px), 180px); + } + + .BGSampleTitle { + display: none; + } + + .tag.excluded:after { + top: unset; + bottom: unset; + } +} + +/*iOS specific*/ +@supports (-webkit-touch-callout: none) { + body { + margin: 0 auto; + } + + #top-bar { + width: 100vw; + } + + #sheld { + margin: unset; + padding: unset; + width: unset; + height: unset; + min-width: unset; + max-width: unset; + min-height: unset; + max-height: unset; + width: 100vw; + width: 100svw; + height: calc(100vh - 40px); + height: calc(100svh - 40px); + padding-right: max(env(safe-area-inset-right), 0px); + padding-left: max(env(safe-area-inset-left), 0px); + padding-bottom: 0; + } + + body.PWA #sheld { + padding-right: max(env(safe-area-inset-right), 2px); + padding-left: max(env(safe-area-inset-left), 2px); + padding-bottom: max(env(safe-area-inset-bottom), 15px); + + } + + #character_popup, + #world_popup, + #left-nav-panel, + #right-nav-panel, + .drawer-content { + width: unset; + height: unset; + min-width: unset; + max-width: unset; + min-height: unset; + max-height: unset; + backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); + left: 0; + right: 0; + top: 0; + margin: 0 auto; + height: calc(100vh - 70px); + height: calc(100svh - 70px); + width: calc(100% - 5px); + max-height: calc(100vh - 70px); + max-height: calc(100svh - 70px); + max-width: calc(100% - 5px); + + } + + #character_popup, + #world_popup, + .drawer-content { + margin-top: 40px; + } + + .scrollableInner { + overflow-y: auto; + overflow-x: hidden; + } + + #horde_model { + height: unset; + } +} \ No newline at end of file diff --git a/public/css/rm-groups.css b/public/css/rm-groups.css new file mode 100644 index 000000000..49457a3e4 --- /dev/null +++ b/public/css/rm-groups.css @@ -0,0 +1,227 @@ +/* GROUP CHATS */ + +.group_pagination { + display: flex; + justify-content: center; + align-items: center; +} + +#rm_group_chats_block .tag.filterByGroups { + display: none; +} + +#rm_button_group_chats h2 { + margin-top: auto; + margin-bottom: auto; + color: rgb(188, 193, 200, 1); + border: 1px solid #333; + background-color: rgba(0, 0, 0, 0.3); + padding: 6px; + border-radius: 10px; +} + +#rm_group_chats_block { + display: none; + align-items: flex-start; + padding: 0 5px; + overflow-y: auto; +} + +#rm_group_chats_block h3, +#rm_group_chats_block h5 { + margin-top: 5px; + margin-bottom: 5px; +} + +#rm_group_buttons>div { + display: flex; + flex-direction: column; +} + +#rm_group_buttons .checkbox { + display: flex; +} + +#rm_group_buttons .checkbox h4 { + display: inline-block; +} + +#rm_group_buttons>input { + + cursor: pointer; + user-select: none; +} + +#rm_group_buttons>input:disabled { + filter: brightness(0.3); + cursor: unset; +} + +#rm_group_members, +#rm_group_add_members { + margin-top: 0.25rem; + margin-bottom: 0.5rem; + border: 1px solid grey; + border-radius: 10px; + background-color: var(--black30a); +} + +#rm_group_buttons_expander { + flex-grow: 1; +} + +#rm_group_delete { + color: rgb(190, 0, 0); +} + +#rm_group_members:empty { + width: 100%; +} + +#rm_group_members:empty::before { + content: 'Group is empty'; + + font-weight: bolder; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + opacity: 0.8; +} + +#rm_group_add_members:empty { + width: 100%; +} + +#rm_group_add_members_header { + display: flex; + flex-direction: row; + width: 100%; + column-gap: 10px; +} + +#rm_group_add_members_header input { + flex: 1; + width: 100%; +} + +#rm_group_add_members:empty::before { + content: 'No characters available'; + + font-weight: bolder; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + opacity: 0.8; +} + +.group_member_icon { + display: flex; + column-gap: 10px; + align-items: center; + justify-content: end; + flex-grow: 1; +} + +.group_member { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + padding: 5px; + border-radius: 10px; +} + +.group_member .group_member_name { + flex-grow: 1; + margin-left: 10px; + overflow: hidden; + text-overflow: ellipsis; + width: calc(100% - 110px); + display: flex; + gap: 5px; + height: 100%; + flex-direction: column; + justify-content: center; +} + +.group_member_icon .flex-container { + gap: 0px; +} + +#rm_group_members .right_menu_button, +#rm_group_add_members .right_menu_button { + padding: 0px; + height: 20px; + display: flex; + align-items: center; +} + +#rm_group_members .right_menu_button[data-action="speak"], +#rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="disable"] { + opacity: 0.4; + filter: brightness(0.5); + transition: all 0.2s ease-in-out; +} + +/* #rm_group_members .right_menu_button[data-action="speak"]:hover, */ +#rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="disable"]:hover { + opacity: inherit; + filter: drop-shadow(0px 0px 5px rgb(243, 166, 65)); +} + +#rm_group_members .group_member.disabled .right_menu_button[data-action="enable"] { + filter: drop-shadow(0px 0px 5px rgb(243, 166, 65)); +} + + +#rm_group_members .right_menu_button[data-action="speak"]:hover { + opacity: inherit; + filter: drop-shadow(0px 0px 5px rgb(153, 255, 153)); +} + +/* Rules for icon display */ +#rm_group_add_members .right_menu_button:not([data-action="add"], [data-action="view"]), +#rm_group_members .right_menu_button[data-action="add"], +#rm_group_members .group_member.disabled .right_menu_button[data-action="disable"], +#rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="enable"] { + display: none; +} + +.group_select { + display: flex; + flex-direction: row; + padding: 5px; + border-radius: 10px; + cursor: pointer; +} + +.group_select:hover { + background-color: var(--white30a); +} + +.group_select .avatar { + flex: 0; +} + +.group_select .group_icon { + width: 20px; + height: 20px; + margin: 0 10px; +} + +.group_select .group_fav_icon { + filter: drop-shadow(0px 0px 1px black); + color: #c5b457; + font-size: 12px; + order: -1; + margin-left: -18px; + margin-top: 3px; +} + +.group_member .avatar { + flex-shrink: 0; +} \ No newline at end of file diff --git a/public/css/select2-overrides.css b/public/css/select2-overrides.css new file mode 100644 index 000000000..52918be45 --- /dev/null +++ b/public/css/select2-overrides.css @@ -0,0 +1,134 @@ +/* Customize the Select2 container */ +.select2-container { + color: var(--SmartThemeBodyColor); +} + +/* Customize the dropdown */ +.select2-dropdown { + background-color: var(--SmartThemeBlurTintColor); + border: 1px solid var(--white30a) !important; + border-radius: 10px; + box-shadow: 0 0 5px black; + text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor); + backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)*2)); + color: var(--SmartThemeBodyColor); + z-index: 40000; +} + +.select2-selection__clear { + color: var(--SmartThemeBodyColor); +} + +.select2-container .select2-selection--multiple .select2-selection__choice__remove { + padding: revert; + border-right: 1px solid var(--white30a); + font-size: 1.1em; + +} + +.select2-container .select2-selection--multiple .select2-selection__choice__display { + padding-left: 5px; +} + +/* Customize the search input */ +.select2-search__field { + background-color: var(--black30a); + color: var(--SmartThemeBodyColor); + border: 1px solid var(--white30a); + border-radius: 7px; + font-family: "Noto Sans", "Noto Color Emoji", sans-serif; + padding: 3px 5px; +} + +/* Customize the selected option */ +.select2-selection--single { + border: 1px solid var(--SmartThemeShadowColor); + border-radius: 4px; + background-color: var(--SmartThemeBlurTintColor); +} + +/* Customize the selected option text */ +.select2-selection__rendered { + color: var(--SmartThemeBodyColor); +} + +/* Customize the option list item */ +.select2-results__option { + color: var(--SmartThemeBodyColor); + background-color: var(--SmartThemeBodyColor); +} + +.select2-container .select2-selection--multiple { + background-color: var(--black30a); + color: var(--SmartThemeBodyColor); + border: 1px solid var(--white30a); + border-radius: 7px; + font-family: "Noto Sans", "Noto Color Emoji", sans-serif; + padding: 3px 5px; +} + +.select2-container.select2-container--focus .select2-selection--multiple { + border: 1px solid var(--white30a); +} + +.select2-container .select2-selection--multiple .select2-selection__choice { + border-radius: 5px; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + color: var(--SmartThemeBodyColor); + background-color: var(--black30a); + border-color: var(--white30a); + font-size: calc(var(--mainFontSize) - 5%); + text-shadow: none !important; +} + +.select2-results .select2-results__option--selectable { + background-color: unset; + color: var(--SmartThemeBodyColor); + opacity: 0.5; + transition: opacity 200ms ease-in-out; + position: relative; +} + +/* Customize the hovered option list item */ +.select2-results .select2-results__option--highlighted.select2-results__option--selectable { + color: var(--SmartThemeBodyColor); + background-color: unset; + opacity: 1; +} + +/* Customize the option list item */ +.select2-results__option { + padding-left: 30px; + /* Add some padding to make room for the checkbox */ +} + +/* Add the custom checkbox */ +.select2-results__option:before { + content: ''; + display: inline-block; + position: absolute; + left: 6px; + top: 50%; + margin-top: -7px; + width: 14px; + height: 14px; + border: 1px solid var(--white30a); + background-color: var(--SmartThemeBlurTintColor); + border-radius: 2px; +} + +.select2-container .select2-selection--multiple .select2-selection__choice__remove { + color: var(--SmartThemeBodyColor); +} + +/* Add the custom checkbox checkmark */ +.select2-results__option--selected.select2-results__option:before { + content: '\2713'; + font-weight: bold; + color: var(--SmartThemeBodyColor); + background-color: var(--SmartThemeBlurTintColor); + text-align: center; + line-height: 14px; +} \ No newline at end of file diff --git a/public/css/st-tailwind.css b/public/css/st-tailwind.css new file mode 100644 index 000000000..5a0c1c52d --- /dev/null +++ b/public/css/st-tailwind.css @@ -0,0 +1,419 @@ +.text_warning { + color: rgb(220 173 16); +} + +.text_danger { + color: var(--fullred); +} + +.m-t-1 { + margin-top: 1em; +} + +.m-t-2 { + margin-top: 2em; +} + +.m-t-3 { + margin-top: 3em; +} + +.m-t-4 { + margin-top: 4em; +} + +.m-t-5 { + margin-top: 5em; +} + +.m-b-1 { + margin-bottom: 1em; +} + +.m-b-2 { + margin-bottom: 2em; +} + +.m-b-3 { + margin-bottom: 3em; +} + +.m-b-4 { + margin-bottom: 4em; +} + +.m-b-5 { + margin-bottom: 5em; +} + +.tooltip { + cursor: help; +} + +.margin-bot-10px, +.marginBot10 { + margin-bottom: 10px; +} + +.marginTop10 { + margin-top: 10px; +} + +.marginBot5 { + margin-bottom: 5px; +} + +.marginTop5 { + margin-top: 5px; +} + +.marginTopBot5 { + margin: 5px 0; +} + +.margin5 { + margin: 5px; +} + +.overflowYAuto { + overflow-y: auto; +} + +.heightMinContent { + height: min-content; +} + +.justifySpaceBetween { + justify-content: space-between; +} + +.alignitemsflexstart { + align-items: flex-start !important; +} + +.alignItemsFlexEnd { + align-items: flex-end !important; +} + +.alignSelfStart { + align-self: start; +} + +.gap5px { + gap: 5px !important; +} + +.gap10px { + gap: 10px !important; +} + +.wide10pMinFit { + width: 10%; + min-width: fit-content; +} + +.wide100pLess70px { + width: calc(100% - 70px); +} + +.wideMax100px { + max-width: 100px; +} + +.widthUnset { + width: unset; +} + +.no-border { + border: none !important; +} + +.no-shadow { + box-shadow: none !important; +} + +.height100pSpaceEvenly { + align-content: space-evenly; + height: 100%; +} + +.height32px { + height: 32px; +} + +.TxtLrgBoldCenter { + text-align: center; + font-size: large; + font-weight: 600; +} + +.margin-right-10px { + margin-right: 10px; +} + + +.success { + color: green; +} + +.failure { + color: red; +} + +.optional { + color: lightgray; +} + +.monospace { + font-family: monospace; +} + +.expander { + flex-grow: 1; +} + +.redOverlayGlow { + color: #800; + opacity: 0.8 !important; + text-shadow: none !important; +} + +.width100p { + width: 100%; +} + +.flex { + display: flex; +} + +.flex-container { + display: flex; + gap: 5px; + flex-wrap: wrap; +} + +.flexNoGap { + gap: unset !important; +} + +.flexGrow { + flex-grow: 1; +} + +.flexnowrap { + flex-wrap: nowrap; +} + +.alignitemscenter { + align-items: center; +} + +.alignitemsstart { + align-items: start; +} + +.overflow-hidden { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.maxWidth200px { + max-width: 200px; +} + +.alignContentFlexStart { + align-content: flex-start; +} + +.overflowHidden { + overflow: hidden; +} + +.padding5 { + padding: 5px; +} + +.padding10 { + padding: 10px; +} + +.margin0 { + margin: 0; +} + +.margin0auto { + margin: 0 auto; +} + +.margin-r5 { + margin-right: 5px; +} + +.flex1 { + flex: 1; +} + +.flex2 { + flex: 2 !important; +} + +.flexFlowColumn { + flex-flow: column; +} + +.wideMinContent { + width: min-content; +} + +.flexWide50p { + flex: 50%; +} + +.wide50p { + width: 50% !important; +} + +.wide25p { + width: 25%; +} + +.wide30p { + width: 30% !important; +} + +.justifyLeft { + text-align: start; + justify-content: left; + margin-left: unset; +} + +.justifyCenter { + justify-content: center; + margin: 0 auto; +} + +.justifyContentSpaceAround { + justify-content: space-around; +} + +.justifyContentFlexStart { + justify-content: flex-start; +} + +.justifyContentFlexEnd { + justify-content: flex-end; +} + +.spaceEvenly { + justify-content: space-evenly; +} + +.spaceBetween { + justify-content: space-between; +} + +.widthNatural { + width: unset !important; + min-width: unset !important; + max-width: unset !important; +} + +.widthFreeExpand { + width: -webkit-fill-available; + width: -moz-available; +} + +.wide100p { + width: 100%; +} + +.wide50p { + width: 50%; +} + +.wide50px { + width: 50px; +} + +.indent20p { + margin-left: 20px; +} + +/*used to fix smallness of certain FontAwesome glyph which break button squareness*/ +/*currently used on: CharList Import*/ + +.faSmallFontSquareFix { + font-size: calc(var(--mainFontSize) *1.25); + width: calc(var(--mainFontSize) *1.95); +} + +.textarea_compact { + font-size: calc(var(--mainFontSize) * 0.9); + line-height: 1.2; +} + +.katex-html { + display: none; +} + +.hoverglow:hover { + opacity: 1 !important; + cursor: pointer; +} + +.debug-red { + border: 1px solid red !important; +} + +.debug-yellow { + border: 1px solid yellow !important; +} + +.debug-green { + border: 1px solid green !important; +} + +.debug-blue { + border: 1px solid blue !important; +} + +.debug-purple { + border: 1px solid purple !important; +} + +.fontsize80p { + font-size: calc(var(--mainFontSize) * 0.8) !important; +} + +.fontsize60p { + font-size: calc(var(--mainFontSize) * 0.6) !important; +} + +.paddingTopBot5 { + padding: 5px 0; +} + +.paddingLeftRight5 { + padding: 0 5px; +} + +.heightFitContent { + height: fit-content; +} + +.widthFitContent { + width: fit-content; +} + +.flexGap5 { + gap: 5px; +} + +.flexGap10 { + gap: 10px; +} + +.opacity1 { + opacity: 1 !important; +} \ No newline at end of file diff --git a/public/css/tags.css b/public/css/tags.css new file mode 100644 index 000000000..534f92495 --- /dev/null +++ b/public/css/tags.css @@ -0,0 +1,167 @@ +#tags_div { + min-width: 0; +} + +.tag_controls { + display: flex; + flex-direction: row; + gap: 5px; + align-items: center; +} + +.tag_view_item { + display: flex; + flex-direction: row; + align-items: baseline; + gap: 10px; + margin-bottom: 5px; +} + +.tag_view_name { + text-align: left; +} + +.tag_view_counter { + text-align: right; + flex: 1; +} + +.tag_delete { + padding-right: 0; + color: var(--SmartThemeBodyColor) !important; +} + +.tag { + border-radius: 5px; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + color: var(--SmartThemeBodyColor); + background-color: var(--black30a); + border-color: var(--white50a); + padding: 0.1rem 0.2rem; + font-size: calc(var(--mainFontSize) - 5%); + display: flex; + flex-direction: row; + align-items: center; + position: relative; + gap: 10px; + width: fit-content; + min-width: 0; + text-shadow: none !important; +} + +.rm_tag_filter .tag:not(.actionable) { + display: none; +} + +.tag.actionable { + border-radius: 50%; + aspect-ratio: 1 / 1; + min-height: calc(var(--mainFontSize) * 2); + min-width: calc(var(--mainFontSize) * 2); + font-size: calc(var(--mainFontSize) * 1); + padding: 0; + display: flex; + justify-content: center; + align-items: center; +} + +.tagListHint { + align-self: center; + display: flex; + margin-right: 10px; +} + +.tag_remove { + cursor: pointer; +} + +.tags { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + gap: 0.2rem; + align-items: flex-end; +} + +#tagList.tags { + margin: 5px 0; +} + +#tagList .tag { + opacity: 0.6; +} + +.tags.tags_inline { + opacity: 0.6; + column-gap: 0.2rem; + row-gap: 0.2rem; + justify-content: flex-start; + max-height: 66%; + overflow: hidden; + flex-basis: 100%; +} + +.tag_name { + text-overflow: ellipsis; + overflow: hidden; + text-align: left; + white-space: nowrap; +} + +.tags_inline .tag { + font-size: calc(var(--mainFontSize) - 15%); + padding: 0 0.15rem; +} + +.rm_tag_controls { + display: flex; + column-gap: 10px; + flex-direction: row; + align-items: flex-start; + margin: 5px; +} + +.rm_tag_filter .tag { + cursor: pointer; + opacity: 0.6; + filter: brightness(0.8); +} + +.tags_view, +.open_alternate_greetings { + margin: 0; + aspect-ratio: 1 / 1; + height: 2rem; +} + +.tag.selected { + opacity: 1 !important; + filter: none !important; +} + +.tag.excluded { + opacity: 1 !important; + filter: saturate(0.4) !important; + border: 1px solid red; +} + +.tag.excluded:after { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + content: "\d7"; + font-size: calc(var(--mainFontSize) *3); + color: red; + line-height: calc(var(--mainFontSize)*1.3); + text-align: center; + text-shadow: 1px 1px 0px black, + -1px -1px 0px black, + -1px 1px 0px black, + 1px -1px 0px black; + opacity: 1; +} \ No newline at end of file diff --git a/public/css/toggle-dependent.css b/public/css/toggle-dependent.css new file mode 100644 index 000000000..45994d690 --- /dev/null +++ b/public/css/toggle-dependent.css @@ -0,0 +1,367 @@ +body.tts .mes[is_user="true"] .mes_narrate, +body.tts .mes[is_system="true"] .mes_narrate { + display: none; +} + +body.sd .sd_message_gen, +body.translate .mes_translate, +body.tts .mes_narrate { + display: inline-block; +} + +body.no-hotswap .hotswap { + display: none !important; +} + +body.no-timer .mes_timer { + display: none !important; +} + +body.no-timestamps .timestamp, +body.no-mesIDDisplay .mesIDDisplay, +body.no-modelIcons .icon-svg { + display: none !important; +} + +/*char list grid mode*/ + +body.charListGrid #rm_print_characters_block { + display: flex; + gap: 5px; + flex-wrap: wrap; + flex-direction: row; + justify-content: space-around; + align-content: flex-start; +} + +body.charListGrid #rm_print_characters_block .character_select { + width: 30%; + align-items: flex-start; + height: min-content; + flex-direction: column; + overflow: hidden; + max-width: 100px; +} + +body.charListGrid #rm_print_characters_block .character_select .ch_name, +body.charListGrid #rm_print_characters_block .group_select .ch_name { + width: 100%; + max-width: 100px; + text-align: center; + font-size: calc(var(--mainFontSize) * .8); +} + +body.charListGrid #rm_print_characters_block .character_select .character_name_block { + width: 100%; +} + +body.charListGrid #rm_print_characters_block .character_select .character_select_container { + width: 100%; + justify-content: center; + max-width: 100px; +} + +body.charListGrid #rm_print_characters_block .group_select { + width: 30%; + height: min-content; + align-items: center !important; + flex-direction: column; + overflow: hidden; + max-width: 100px; +} + +body.charListGrid #rm_print_characters_block .group_select .group_name_block { + width: 100%; +} + +body.charListGrid #rm_print_characters_block .ch_description, +body.charListGrid #rm_print_characters_block .tags_inline, +body.charListGrid #rm_print_characters_block .character_version, +body.charListGrid #rm_print_characters_block .ch_avatar_url { + display: none; +} + +/*big avatars mode page-wide changes*/ + +body.big-avatars .character_select .avatar { + flex: unset; +} + +body:not(.big-avatars) .avatar { + border-radius: 50%; +} + +body.big-avatars .avatar { + width: 60px; + height: 90px; + /* width: unset; */ + border-style: none; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + /* align-self: unset; */ + overflow: visible; + border-radius: 10px; + flex: 1 +} + +body.big-avatars #user_avatar_block .avatar, +body.big-avatars #user_avatar_block .avatar_upload { + height: 90px; + width: 60px; + border-radius: 10px; +} + +body.big-avatars #user_avatar_block .avatar img { + height: 90px; + width: 60px; +} + +body.big-avatars .avatar img { + width: 60px; + height: 90px; + object-fit: cover; + object-position: center; + border: 1px solid var(--black30a); + border-radius: 10px; +} + +body:not(.big-avatars) .avatar_collage { + min-width: 50px; + aspect-ratio: 1 / 1; +} + +body:not(.big-avatars) .avatar_collage img { + border-radius: 0% !important; +} + +body.big-avatars .avatar_collage { + min-width: 60px; + max-width: 60px; + aspect-ratio: 2 / 3; +} + +body.big-avatars .ch_description { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + white-space: normal; + text-overflow: unset; +} + +/* border radius for big avatars collages */ + +body.big-avatars .collage_2 .img_1 { + border-radius: 10px 0 0 10px !important; +} + +body.big-avatars .collage_2 .img_2 { + border-radius: 0 10px 10px 0 !important; +} + +body.big-avatars .collage_3 .img_1 { + border-radius: 10px 0 0 0 !important; +} + +body.big-avatars .collage_3 .img_2 { + border-radius: 0 10px 0 0 !important; +} + +body.big-avatars .collage_3 .img_3 { + border-radius: 0 0 10px 10px !important; +} + +body.big-avatars .collage_4 .img_1 { + border-radius: 10px 0 0 0 !important; +} + +body.big-avatars .collage_4 .img_2 { + border-radius: 0 10px 0 0 !important; +} + +body.big-avatars .collage_4 .img_3 { + border-radius: 0 0 0 10px !important; +} + +body.big-avatars .collage_4 .img_4 { + border-radius: 0 0 10px 0 !important; +} + + + +/*bubble chat style*/ + +body.bubblechat .mes { + padding: 10px; + border-radius: 10px; + background-color: var(--SmartThemeBotMesBlurTintColor); + margin-bottom: 5px; + border: 1px solid var(--white30a); +} + +body.bubblechat .mes[is_user="true"] { + background-color: var(--SmartThemeUserMesBlurTintColor); +} + + +/* Document Style */ + +body.documentstyle #chat .mes:not(.last_mes) { + padding: 0 10px; +} + +body.documentstyle .last_mes { + padding-top: 0; +} + +body.documentstyle #chat .mes .mes_text { + padding: 0; +} + +body.documentstyle #chat .mes .mes_block { + margin-right: 30px; +} + +body.documentstyle #chat .mes .mes_text { + margin-left: 20px; +} + +body.documentstyle #chat .last_mes .mes_text { + margin-left: 20px; + min-height: 70px; +} + +body.documentstyle #chat .last_mes:has(> .del_checkbox[style*="display: block"]) .mes_text { + margin-left: 0px; +} + +body.documentstyle #chat .last_mes .swipe_left { + left: 5px; +} + +body.documentstyle #chat .mes .mesAvatarWrapper, +body.documentstyle #chat .mes .mes_block .ch_name .name_text, +body.documentstyle #chat .mes .mes_block .ch_name .timestamp, +body.documentstyle .mes:not(.last_mes) .ch_name .mes_buttons { + display: none !important; +} + +/*FastUI blur removal*/ + +body.no-blur * { + backdrop-filter: unset !important; +} + +body.no-blur #send_form.no-connection { + background-color: rgba(100, 0, 0, 0.9) !important; +} + +body.no-blur #bg1, +body.no-blur #bg_custom { + filter: unset; + +} + +body:not(.bubblechat).no-blur #chat, +body.no-blur #top-bar, +body.no-blur #send_form { + background-color: var(--SmartThemeBlurTintColor) !important; +} + +body.no-blur #options, +body.no-blur .ui-widget-content, +body.no-blur #floatingPrompt, +body.no-blur #extensionsMenu, +body.no-blur .list-group, +body.no-blur #character_popup, +body.no-blur #world_popup, +body.no-blur #dialogue_popup, +body.no-blur #select_chat_popup, +body.no-blur .drawer-content, +body.no-blur .select2-results__options { + background-color: black !important; +} + +/* wAIfu mode*/ + +body.waifuMode #top-bar { + border-radius: 0 0 20px 20px; + border: 1px solid var(--grey30a); +} + +body.waifuMode #sheld { + height: 40vh; + height: 40svh; + top: calc(100% - 40vh); + bottom: 0; +} + +body.waifuMode #chat { + border-top: 1px solid var(--grey30a); + border-radius: 20px 20px 0 0; +} + +body.waifuMode #expression-wrapper { + justify-content: center; +} + +body.waifuMode .expression-holder { + max-height: 90vh; + max-width: 90vw; + height: 90vh; + width: fit-content; + bottom: 0; + filter: drop-shadow(2px 2px 2px #51515199); + z-index: 2; + margin: 0 auto; + left: 0; + right: 0; +} + +body.waifuMode .zoomed_avatar { + min-width: 100px; + min-height: 100px; + max-height: 90vh; + max-width: 90vh; + width: calc((100vw - var(--sheldWidth)) /2); + position: absolute; + padding: 0; + filter: drop-shadow(2px 2px 2px #51515199); + z-index: 29; + overflow: hidden; + display: none; + left: 0; + right: 0; + margin: 0 auto; + top: 50px; + aspect-ratio: 2 / 3; +} + +/* movingUI*/ + +body.movingUI .drag-grabber { + display: inline; +} + +body.movingUI #sheld, +body.movingUI .drawer-content, +body.movingUI #expression-holder, +body.movingUI .zoomed_avatar, +body.movingUI #floatingPrompt, +body.movingUI #groupMemberListPopout { + resize: both; +} + +#expression-image.default, +#expression-holder:has(.default) { + height: 120px; + margin-top: 0; + top: 50px; +} + +/*No Text Shadows Mode*/ + +body.noShadows * { + text-shadow: none !important; +} \ No newline at end of file diff --git a/public/css/world-info.css b/public/css/world-info.css new file mode 100644 index 000000000..835d0661b --- /dev/null +++ b/public/css/world-info.css @@ -0,0 +1,181 @@ +.world_info_select_block { + display: flex; + flex-direction: row; + align-items: baseline; + gap: 5px; +} + +.budget_cap_note { + flex-basis: 100%; + line-height: 0.1; +} + +#world_popup { + min-height: 100px; + min-width: 100px; + left: 0; + right: 0; + flex-direction: column; + z-index: 3010; + overflow-y: hidden; +} + +.WIEntryContentAndMemo { + width: 100% !important; + flex-wrap: nowrap !important; +} + +.WIEntryContentAndMemo .world_entry_thin_controls { + flex: 1; +} + +#world_popup_bottom_holder { + display: flex; + flex-flow: row; + justify-content: space-evenly; + align-items: center; +} + +#world_popup_bottom_holder div { + width: fit-content; + user-select: none; + opacity: 0.8; +} + +.world_popup_logo_block { + display: flex; + align-items: center; +} + +#world_popup_header { + display: flex; + flex-direction: row; + align-items: center; +} + +#world_popup_header h3 { + margin: 0; +} + +#form_rename_world { + display: flex; + align-items: center; + opacity: 0.8; + gap: 5px; +} + +#form_rename_world input[type="submit"] { + cursor: pointer; +} + +#form_world_import { + display: none; +} + +#world_popup_header h5 { + display: inline-block; +} + +.world_popup_expander { + flex-grow: 1; +} + +#world_popup_entries_list { + flex-grow: 1; + overflow-y: auto; +} + +#world_popup_entries_list:empty { + width: 100%; + height: 100%; +} + +#world_popup_entries_list:empty::before { + content: 'No entries exist. Try creating one!'; + font-size: calc(var(--mainFontSize) + .5rem); + font-weight: bolder; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + opacity: 0.8; +} + +.world_entry_form_control { + display: flex; + flex-direction: column; +} + +.world_entry_thin_controls { + display: flex; + flex-direction: row; +} + +/* .world_entry_thin_controls>div { + flex: 1; +} */ + +.world_entry_form_control label h4 { + margin-bottom: 0; + margin-top: 0; +} + +.world_entry_form_control label h5 { + margin-top: 3px; + margin-bottom: 3px; +} + +.world_entry_form_control textarea { + height: auto; + margin-top: 0; + margin-bottom: 0; + min-height: 32px; +} + +.delete_entry_button { + height: min-content; +} + +.world_entry_form_control.world_entry_form_horizontal { + flex-direction: row; + align-items: center; + flex-wrap: wrap; +} + +.world_entry_form_control input[type=button] { + cursor: pointer; +} + +.world_entry_form_horizontal h5 { + margin: 0 1rem; +} + +.world_entry_form_control .checkbox { + align-items: center; + display: flex; + flex-direction: row; + column-gap: 10px; +} + +.world_entry_form_control .checkbox h4 { + margin: 0; + display: inline-block; +} + +.world_entry_form_radios label { + margin-left: 0; +} + +.world_entry_form_radios h4 { + display: inline; +} + +#world_popup h5 { + color: var(--grey70); +} + +/* possible place for WI Entry header styling */ +/* .world_entry_form .inline-drawer-header { + background-color: var(--SmartThemeShadowColor); +} */ \ No newline at end of file diff --git a/public/i18n.json b/public/i18n.json index 887aaa7cf..421b92f86 100644 --- a/public/i18n.json +++ b/public/i18n.json @@ -2,7 +2,8 @@ "lang": [ "zh-cn", "ja-jp", - "ko-kr" + "ko-kr", + "ru-ru" ], "zh-cn": { "clickslidertips": "点击滑块右侧数字可手动输入", @@ -362,8 +363,8 @@ "Not Connected": "未连接", "Persona Management": "用户角色设置", "Persona Description": "用户角色描述", - "Before Character Card": "角色卡之前", - "After Character Card": "角色卡之后", + "In Story String / Chat Completion: Before Character Card": "在故事串中 / Chat Completion: 角色卡之前", + "In Story String / Chat Completion: After Character Card": "在故事串中 / Chat Completion: 角色卡之后", "Top of Author's Note": "作者注释之前", "Bottom of Author's Note": "作者注释之后", "How do I use this?": "用户角色设置说明", @@ -914,8 +915,8 @@ "Not Connected": "NEEDS TRANSLATION", "Persona Management": "NEEDS TRANSLATION", "Persona Description": "NEEDS TRANSLATION", - "Before Character Card": "NEEDS TRANSLATION", - "After Character Card": "NEEDS TRANSLATION", + "In Story String / Chat Completion: Before Character Card": "NEEDS TRANSLATION", + "In Story String / Chat Completion: After Character Card": "NEEDS TRANSLATION", "Top of Author's Note": "NEEDS TRANSLATION", "Bottom of Author's Note": "NEEDS TRANSLATION", "How do I use this?": "NEEDS TRANSLATION", @@ -1471,8 +1472,8 @@ "Not Connected": "접속되지 않음", "Persona Management": "주인공 관리", "Persona Description": "주인공 묘사", - "Before Character Card": "캐릭터 카드 앞에", - "After Character Card": "캐릭터 카드 다음에", + "In Story String / Chat Completion: Before Character Card": "스토리 문자열에서 / 문장완성: 캐릭터 카드 앞에", + "In Story String / Chat Completion: After Character Card": "스토리 문자열에서 / 문장완성: 캐릭터 카드 다음에", "Top of Author's Note": "글쓴이 쪽지 위에", "Bottom of Author's Note": "글쓴이 쪽지 밑에", "How do I use this?": "이건 어떻게 써먹나요?", @@ -1671,5 +1672,560 @@ "Select this as default persona for the new chats.": "새로 열리는 채팅에서 기본 주인공으로 설정", "Change persona image": "주인공 아바타 바꾸기", "Delete persona": "주인공 삭제하기" + }, + "ru-ru": { + "clickslidertips": "Можно установить вручную, использовав цифру рядом с ползунком", + "kobldpresets": "Предустановки Kobold", + "guikoboldaisettings": "Интерфейс KoboldAI", + "novelaipreserts": "Предустановки NovelAI", + "default": "По умолчанию", + "openaipresets": "Предустановки OpenAI", + "text gen webio(ooba) presets": "Предустановки WebUI(ooba)", + "response legth(tokens)": "Длина ответа (в токенах)", + "select": "Выбрать", + "context size(tokens)": "Размер контекста (в токенах)", + "unlocked": "Неограниченный", + "only select modls support context sizes greater than 2048 tokens. proceed only is you know you're doing": "Только отдельные модели поддерживают контекст, превышающий 2048 токенов. Используйте только если понимаете, что делаете.", + "rep.pen": "Rep. Pen.", + "rep.pen range": "Диапазон Rep. Pen.", + "temperature": "Температура", + "Encoder Rep. Pen.": "Расшифровщик Rep. Pen.", + "No Repeat Ngram Size": "No Repeat Ngram Size", + "Min Length": "Минимальная длина", + "OpenAI Reverse Proxy": "Прокси с OpenAI", + "Alternative server URL (leave empty to use the default value).": "Альтернативный URL сервера (оставьте пустым для стандартного значения)", + "Remove your real OAI API Key from the API panel BEFORE typing anything into this box": "Удалите свой личный OAI API Key из панели API прежде, чем вносить сюда ЧТО УГОДНО", + "We cannot provide support for problems encountered while using an unofficial OpenAI proxy": "Мы не сможем предоставить помощь с проблемами, с которыми вы столкнетесь при использовании неофициальных прокси для OpenAI", + "Legacy Streaming Processing": "Старый способ потокового вывода текста", + "Enable this if the streaming doesn't work with your proxy": "Включите это, если потоковый вывод текста не работает с вашим прокси", + "Context Size (tokens)": "Размер контекста (в токенах)", + "Max Response Length (tokens)": "Максимальная длина ответа (в токенах)", + "Temperature": "Temperature", + "Frequency Penalty": "Frequency Penalty", + "Presence Penalty": "Presence Penalty", + "Top-p": "Top-p", + "Display bot response text chunks as they are generated": "Отображать ответ ИИ по мере генерации текста", + "Top A": "Top-a", + "Typical Sampling": "Typical Sampling", + "Tail Free Sampling": "Tail Free Sampling", + "Rep. Pen. Slope": "Rep. Pen. Slope", + "Single-line mode": "Режим одной строки", + "Top K": "Top-k", + "Top P": "Top-p", + "Do Sample": "Do Sample", + "Add BOS Token": "Добавить BOS-токен", + "Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative.": "Добавлять BOS-токен в начале инструкции. Выключение этого может сделать ответы более креативными. ", + "Ban EOS Token": "Заблокировать EOS-токен", + "Ban the eos_token. This forces the model to never end the generation prematurely": "Блокировка EOS-токена вынудит модель никогда не завершать генерацию преждевременно", + "Skip Special Tokens": "Пропускать специальные токены", + "Beam search": "Поиск Beam", + "Number of Beams": "Количество Beam", + "Length Penalty": "Length Penalty", + "Early Stopping": "Преждевременная остановка", + "Contrastive search": "Contrastive search", + "Penalty Alpha": "Penalty Alpha", + "Seed": "Зерно", + "Inserts jailbreak as a last system message.": "Вставлять JailBreak последним системным сообщением.", + "This tells the AI to ignore its usual content restrictions.": "Сообщает AI о необходимости игнорировать стандартные ограничения контента.", + "NSFW Encouraged": "Поощрять NSFW", + "Tell the AI that NSFW is allowed.": "Сообщает AI, что ему позволено генерировать NSFW.", + "NSFW Prioritized": "Предпочитать NSFW", + "NSFW prompt text goes first in the prompt to emphasize its effect.": "Отправлять NSFW-инструкцию в начале для усиления его эффекта", + "Streaming": "Потоковый вывод текста", + "Display the response bit by bit as it is generated.": "Отображать ответ по кускам в процессе генерации.", + "When this is off, responses will be displayed all at once when they are complete.": "Если данная функция отключена, ответ будет отображен полностью после генерации.", + "Enhance Definitions": "Улучшенная узнаваемость", + "Use OAI knowledge base to enhance definitions for public figures and known fictional characters": "Позволяет использовать базу знаний, улучшающую узнаваемость ИИ публичных лиц и вымышленных персонажей", + "Wrap in Quotes": "Заключать в кавычки", + "Wrap entire user message in quotes before sending.": "Заключить всё сообщение пользователя в кавычки перед отправкой.", + "Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.", + "Main prompt": "Основная инструкция", + "The main prompt used to set the model behavior": "Основная инструкция, используемая для установки поведения модели", + "NSFW prompt": "NSFW-инструкция", + "Prompt that is used when the NSFW toggle is on": "Инструкция, отправляемая ИИ при включенном поощрении NSFW.", + "Jailbreak prompt": "Инструкция для JailBreak", + "Prompt that is used when the Jailbreak toggle is on": "Инструкция, отправляемая ИИ при включенном JailBreak.", + "Impersonation prompt": "Инструкция для перевоплощения", + "Prompt that is used for Impersonation function": "Инструкция, отправляемая ИИ для генерации действий за пользователя", + "Logit Bias": "Logit Bias", + "Helps to ban or reenforce the usage of certain words": "Позволяет запретить или поощрять использование определенных слов", + "View / Edit bias preset": "Посмотреть/Настроить предустановку для bias", + "Add bias entry": "Добавить инструкцию в Bias", + "Jailbreak activation message": "Сообщение об активации JailBreak", + "Message to send when auto-jailbreak is on.": "Сообщение, отправляемое когда автоматический JailBreak включен.", + "Jailbreak confirmation reply": "Подтверждение JailBreak", + "Bot must send this back to confirm jailbreak": "Это сообщение будет отправлено ИИ при успешном включении JailBreak.", + "Character Note": "Заметки о персонаже", + "Influences bot behavior in its responses": "Влияет на поведение ИИ и его ответы.", + "API": "API", + "KoboldAI": "KoboldAI", + "Use Horde": "Использовать Horde", + "API url": "API URL", + "Register a Horde account for faster queue times": "Заведите учетную запись Horde для ускорения генерации", + "Learn how to contribute your idle GPU cycles to the Hord": "Узнайте подробнее о том, как использовать время простоя GPU для Hord", + "Adjust context size to worker capabilities": "Уточнить размер контекста для возможностей рабочих", + "Adjust response length to worker capabilities": "Уточнить длинну ответа для возможностей рабочий", + "API key": "API-ключ", + "Register": "Регист", + "For privacy reasons": "В целях конфиденциальности API-ключ будет скрыт после перезагрузки страницы", + "Model": "Модель", + "Hold Control / Command key to select multiple models.": "Удерживайте Control / Command для выбора нескольких моделей.", + "Horde models not loaded": "Модели Horde не загружены", + "Not connected": "Нет подключения", + "Novel API key": "API-ключ для NovelAI", + "Follow": "Следуйте", + "these directions": "данным инструкциям", + "to get your NovelAI API key.": "чтобы получить свой API-ключ от NovelAI", + "Enter it in the box below": "Введите это в окошко ниже", + "Novel AI Model": "Модель NovelAI", + "Euterpe": "Euterpe", + "Krake": "Krake", + "No connection": "Нет подключения", + "oobabooga/text-generation-webui": "", + "Make sure you run it with": "Убедитесь, что при запуске указали аргумент --api", + "Blocking API url": "Блокирующий API URL", + "Streaming API url": "Потоковый API URL", + "to get your OpenAI API key.": "для получения API-ключа OpenAI", + "OpenAI Model": "Модель OpenAI", + "View API Usage Metrics": "Посмотреть статистику использования API", + "Bot": "Бот:", + "Connect to the API": "Соединение с API", + "Auto-connect to Last Server": "Автоматическое подключение к последнему серверу", + "View hidden API keys": "Посмотреть скрытые API-ключи", + "Advanced Formatting": "Расширенное форматирование", + "AutoFormat Overrides": "Замена АвтоФормата", + "Disable description formatting": "Отключить форматирование описания", + "Disable personality formatting": "Отключить форматирование личности", + "Disable scenario formatting": "Отключить форматирование сценария", + "Disable example chats formatting": "Отключить форматирование примеров чата", + "Disable chat start formatting": "Отключить форматирование начала чата", + "Custom Chat Separator": "Пользовательское разделение чата", + "Instruct mode": "Режим Instruct", + "Enabled": "Включен", + "Wrap Sequences with Newline": "Отделять последовательности красной строкой", + "Include Names": "Показывать имена", + "System Prompt": "Системная инструкция", + "Input Sequence": "Input Sequence", + "Output Sequence": "Output Sequence", + "System Sequence": "System Sequence", + "Stop Sequence": "Stop Sequence", + "Context Formatting": "Форматирование контекста", + "Tokenizer": "Токенайзер", + "None / Estimated": "Отсутствует/Приблизительно", + "Sentencepiece (LLaMA)": "Sentencepiece(LLaMA)", + "Token Padding": "Заполнение токенов", + "Always add character's name to prompt": "Всегда добавлять имя персонажа в инструкции", + "Keep Example Messages in Prompt": "Сохранять примеры сообщений в инструкции", + "Remove Empty New Lines from Output": "Удалять пустые строчки из вывода", + "Pygmalion Formatting": "Форматирование Pygmalion", + "Disabled for all models": "Выключено для всех моделей", + "Automatic (based on model name)": "Автоматически (выбор по названию модели)", + "Enabled for all models": "Включить для всех моделей", + "Multigen": "Мултиген", + "First chunk (tokens)": "Первый отрезок (в токенах)", + "Next chunks (tokens)": "Следующий отрезок (в токенах)", + "Anchors Order": "Порядок Anchors", + "Character then Style": "Персонаж после Стиля", + "Style then Character": "Стиль после Персонажа", + "Character Anchor": "Anchors Персонажа", + "Style Anchor": "Стиль Anchors", + "World Info": "Информация о мире", + "Scan Depth": "Глубина сканирования", + "depth": "глубина", + "Token Budget": "Объем токенов", + "budget": "объем", + "Recursive scanning": "Рекурсивное сканирование", + "Soft Prompt": "Мягкая инструкция", + "About soft prompts": "О мягких инструкциях", + "None": "Отсутствует", + "User Settings": "Настройки пользователя", + "UI Customization": "Настройки UI", + "Avatar Style": "Стиль аватаров", + "Circle": "Круглые", + "Rectangle": "Прямоугольные", + "Chat Style": "Стиль чата", + "Default": "По умолчанию", + "Bubbles": "Пузыри", + "Chat Width (PC)": "Ширина чата (на PC):", + "No Blur Effect": "Отключить эффект размытия", + "No Text Shadows": "Отключить тень текста", + "Waifu Mode": "!!!РЕЖИМ ВАЙФУ!!!", + "Message Timer": "Таймер сообщений", + "Characters Hotswap": "Смена персонажей на лету", + "Movable UI Panels": "Перемещение панелей интерфейса", + "Reset Panels": "Сбросить панели", + "UI Colors": "Цвета интерфейса", + "Main Text": "Основной текст", + "Italics Text": "Курсивный текст", + "Quote Text": "Текст в кавычках", + "Shadow Color": "Цвет теней", + "FastUI BG": "Фон FastUI", + "Blur Tint": "Оттенок размытия", + "Font Scale": "Размер текста", + "Blur Strength": "Сила размытия", + "Text Shadow Width": "Размер теней текста", + "UI Theme Preset": "Предустановки интерфейса", + "Power User Options": "Продвинутые параметры", + "Swipes": "Свайвы", + "Background Sound Only": "Только фоновый звук", + "Auto-load Last Chat": "Автоматически загружать последий чат", + "Auto-save Message Edits": "Автоматически сохранять отредактированные сообщения", + "Auto-fix Markdown": "Автоматическое исправление подчеркиваний", + "Allow : in bot messages": "Разрешить : в сообщениях ИИ", + "Auto-scroll Chat": "Автоматическая прокрутка чата", + "Render Formulas": "Рендер формул", + "Send on Enter": "Отправка на Enter", + "Always disabled": "Всегда выключена", + "Automatic (desktop)": "Автоматически (системные настройки)", + "Always enabled": "Всегда включена", + "Name": "Имя", + "Your Avatar": "Ваш Аватар", + "Extensions API:": "API для расширений", + "SillyTavern-extras": "SillyTavern-extras", + "Auto-connect": "Автоматическое соединение", + "Active extensions": "Актививные расширения", + "Extension settings": "Настройки расширений", + "Description": "Описание", + "First message": "Первое сообщение", + "Group Controls": "Контроль группы", + "Group reply strategy": "Сортировка ответов в группе", + "Natural order": "Обычный порядок", + "List order": "Порядок по листу", + "Allow self responses": "Разрешить ответ себе", + "Auto Mode": "Автоматический режим", + "Add Members": "Добавить членов", + "Current Members": "Текущие члены", + "text": "текст", + "Delete": "Удалить", + "Cancel": "Отменить", + "Advanced Defininitions": "Улучшенная узнаваемость", + "Personality summary": "Личная сводка", + "A brief description of the personality": "Краткое описание личности", + "Scenario": "Сценарий", + "Circumstances and context of the dialogue": "Обстоятельства и контекст диалога", + "Talkativeness": "Разговорчивость", + "How often the chracter speaks in": "Как часто персонаж говорит", + "group chats!": "в груповых чатах", + "Shy": "Застенчивый", + "Normal": "Обычный", + "Chatty": "Разговорчивый", + "Examples of dialogue": "Примеры диалога", + "Forms a personality more clearly": "Определите личность более точно", + "Save": "Сохранить", + "World Info Editor": "Редактирование информации о мире", + "New summary": "Новая переменная", + "Export": "Экспорт", + "Delete World": "Удалить мир", + "Chat History": "История чата", + "Group Chat Scenario Override": "Замещение сценария группового чата", + "All group members will use the following scenario text instead of what is specified in their character cards.": "Все участники группы будут использовать следующий сценарий, вместо обозначенного в карточках персонажей", + "Keywords": "Ключевые слова", + "Separate with commas": "Разделять запятыми", + "Secondary Required Keywords": "Вторичные ключевые слова", + "Content": "Содержание", + "What this keyword should mean to the AI": "Значение данного ключевого слова для ИИ", + "Memo/Note": "Напоминание", + "Not sent to AI": "Не отправлять ИИ", + "Constant": "Постоянно", + "Selective": "Выборочно", + "Before Char": "Перед Персонажем", + "After Char": "После Персонажа", + "Insertion Order": "Порядок внесения", + "Tokens:": "Токены", + "Disable": "Отключено", + "${characterName}": "${имяПерсонажа}", + "CHAR": "ПЕРСОНАЖ", + "is typing": "Печатает...", + "Back to parent chat": "Вернуться в основной чат", + "Save bookmark": "Сохранить закладку", + "Convert to group": "Превратить в группу", + "Start new chat": "Начать новый чат", + "View past chats": "Посмотреть прошлые чаты", + "Delete messages": "Удалить сообщения", + "Impersonate": "Перевоплощение", + "Regenerate": "Повторная генерация", + "PNG": "PNG", + "JSON": "JSON", + "WEBP": "WEBP", + "presets": "Предустановки", + "Message Sound": "Звук сообщения", + "Author's Note": "Авторские заметки", + "Send Jailbreak": "Отправлять JailBreak", + "Replace empty message": "Заменять пустые сообщения", + "Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.", + "NSFW avoidance prompt": "Инструкции для избегания NSFW", + "Prompt that is used when the NSFW toggle is off": "Инструкции, используемые, когда поощрение NSFW отключено", + "Advanced prompt bits": "Дополнительные инструкции", + "World Info format": "Шаблон форматирования Информации о мире", + "Wraps activated World Info entries before inserting into the prompt. Use {0} to mark a place where the content is inserted.": "Выделяет активную Информацию о мире перед внесением в инструкции. Используйте {0} чтобы уточнить место с необходимой информацией.", + "Unrestricted maximum value for the context slider": "Неограниченное максимальное значение для ползунка с размером контекста", + "Chat Completion Source": "Источник для Chat Completion", + "Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde", + "Review the Privacy statement": "Посмотреть Privacy statement", + "Learn how to contribute your idel GPU cycles to the Horde": "Изучите, как использовать GPU в состоянии простоя на благо Horde", + "Trusted workers only": "Только доверенные рабочие", + "For privacy reasons, your API key will be hidden after you reload the page.": "По причинам безопасности ваш API-ключ будет скрыт после перезагрузки страницы.", + "-- Horde models not loaded --": "--Модель Horde не загружена--", + "Example: http://127.0.0.1:5000/api ": "Пример: http://127.0.0.1:5000/api", + "No connection...": "Нет соединения...", + "Get your NovelAI API Key": "Получите свой API-ключ для NovelAI", + "KoboldAI Horde": "KoboldAI Horde", + "Text Gen WebUI (ooba)": "Text Gen WebUI (ooba)", + "NovelAI": "NovelAI", + "Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale)": "Дополнение диалога (OpenAI, Claude, Window/OpenRouter, Scale)", + "OpenAI API key": "API-ключ для OpenAI", + "Trim spaces": "Обрезать пробелы", + "Trim Incomplete Sentences": "Обрезать неоконченные предложения", + "Include Newline": "Использовать красную строку", + "Non-markdown strings": "Неподчеркиваемые Strings", + "Replace Macro in Sequences": "Заменить макросы в последовательности", + "Presets": "Предустановки", + "Separator": "Разделитель", + "Start Reply With": "Начинать ответ с", + "Show reply prefix in chat": "Показывать префиксы ответов в чате", + "Worlds/Lorebooks": "Миры/Сведения", + "Active World(s)": "Активные миры", + "Character Lore Insertion Strategy": "Порядок включения сведений", + "Sorted Evenly": "Равномерная сортировка", + "Character Lore First": "Сначала сведения о персонаже", + "Global Lore First": "Сначала общие сведения", + "-- World Info not found --": "Информация о Мире не найдена", + "Recursive Scan": "Рекурсивное сканирование", + "Case Sensitive": "Учитывать регистр", + "Match whole words": "Только полное совпадение", + "World/Lore Editor": "Редактировать Мир/Сведения", + "--- None ---": "---Отсутствует---", + "Comma seperated (ignored if empty)": "Разделение запятыми (не используется, если оставлено пустым)", + "Use Probability": "Использовать вероятность", + "Exclude from recursion": "Исключить из рекурсии", + "Position:": "Положение:", + "Before Char Defs": "Перед определением Персонажа", + "After Char Defs": "После определения Персонажа", + "Before AN": "Перед AN", + "After AN": "После AN", + "Order:": "Порядок:", + "Probability:": "Вероятность:", + "Delete Entry": "Удалить запись:", + "User Message Blur Tint": "Оттенок размытия сообщения пользователя", + "AI Message Blur Tint": "Оттенок размытия сообщения ИИ", + "Chat Style:": "Стиль чата", + "Chat Width (PC):": "Ширина чата (для ПК)", + "Chat Timestamps": "Временные обозначения в чате", + "Message IDs": "ID сообщений", + "Prefer Character Card Prompt": "Предпочитать инструкции из Карточки Персонажа", + "Prefer Character Card Jailbreak": "Предпочитать JailBreak из Карточки Персонажа", + "Press Send to continue": "Нажатие Отправить для продолжения", + "Log prompts to console": "Выводы журнала в консоли", + "Never resize avatars": "Никогда не менять размер аватаров", + "Show avatar filenames": "Показывать названия файлов аватаров", + "Import Card Tags": "Импорт меток Карточки", + "Confirm message deletion": "Подтверждение удаления сообщений", + "Spoiler Free Mode": "Режим без спойлеров", + "Auto-swipe": "Автоматические свайпы", + "Minimum generated message length": "Минимальная длина сгенерированных сообщений", + "Blacklisted words": "Запрещенные слова", + "Blacklisted word count to swipe": "Количество запрещенных слов для свайпа", + "Reload Chat": "Перезагрузить чат", + "Not Connected": "Не подключено", + "Persona Management": "Управление Персоной", + "Persona Description": "Описание Персоны", + "In Story String / Chat Completion: Before Character Card": "В строке истории / Дополнение диалога: Перед Карточкой Персонажа", + "In Story String / Chat Completion: After Character Card": "В строке истории / Дополнение диалога: После Карточки Персонажа", + "Top of Author's Note": "Перед Авторскими Заметками", + "Bottom of Author's Note": "После Авторских Заметок", + "How do I use this?": "Как мне это использовать?", + "More...": "Узнать больше...", + "Link to World Info": "Ссылка на информацию о мире", + "Import Card Lore": "Импортировать Карточку Сведений", + "Scenario Override": "Замещение сценария", + "Rename": "Переименовать", + "Character Description": "Описание персонажа", + "Creator's Notes": "Заметки создателя", + "A-Z": "A-Z", + "Z-A": "Z-A", + "Newest": "Новейшие", + "Oldest": "Старейшие", + "Favorites": "Любимые", + "Recent": "Последние", + "Most chats": "Больше всего чатов", + "Least chats": "Меньше всего чатов", + "Back": "Назад", + "Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct mode)": "Замещение инструкций (Для OpenAI/Claude/Scale API, Window/OpenRouter, и Режима Instruct)", + "Insert {{original}} into either box to include the respective default prompt from system settings.": "Внесите {{original}} в любое поле для внесения стандартных инструкций из системных настроек", + "Main Prompt": "Главные инструкции", + "Jailbreak": "JailBreak", + "Creator's Metadata (Not sent with the AI prompt)": "Сведения о создателе (не отправляются ИИ с инструкциями)", + "Everything here is optional": "Всё в данных полях опционально", + "Created by": "Создано", + "Character Version": "Версия Персонажа", + "Tags to Embed": "Теги для встраивания", + "How often the character speaks in group chats!": "Как часто персонаж говорит в групповых чатах", + "Important to set the character's writing style.": "Важные замечания для стиля написания персонажа", + "ATTENTION!": "ВНИМАНИЕ!", + "Samplers Order": "Порядок семплирования", + "Samplers will be applied in a top-down order. Use with caution.": "Семплирование будет применено в порядке сверху-вниз. Используйте с осторожностью.", + "Repetition Penalty": "Наказание за повторы", + "Epsilon Cutoff": "Epsilon Cutoff", + "Eta Cutoff": "Eta Cutoff", + "Rep. Pen. Range.": "Размер наказания за повторы", + "Rep. Pen. Freq.": "Частота наказания за повторы", + "Rep. Pen. Presence": "Наличие наказания за повторы", + "Enter it in the box below:": "Введите в поле ниже:", + "separate with commas w/o space between": "разделять запятыми без пробелов между:", + "Document": "Документ", + "Suggest replies": "Предлагать ответы", + "Show suggested replies. Not all bots support this.": "Показывать предлагаемые ответы. Не все боты поддерживают это.", + "Use 'Unlocked Context' to enable chunked generation.": "Использовать 'Безлимитный контекст' для активации кусочной генерации", + "It extends the context window in exchange for reply generation speed.": "Увеличивает размер контекста в обмен на скорость генерации.", + "Continue": "Пролдолжить", + "Editing:": "Изменения", + "AI reply prefix": "Префикс Ответ ИИ", + "Custom Stopping Strings": "Настройка ограничивающий нитей", + "JSON serialized array of strings": "JSON ориентированный набор нитей", + "words you dont want generated separated by comma ','": "слова которые вы не хотите при генерации здесь, разделенные запятой", + "Extensions URL": "URL расширений ", + "API Key": "Ключ API", + "Enter your name": "Введите свое имя", + "Name this character": "Назовите этого персонажа", + "Search / Create Tags": "Искать / Создать тэги", + "Describe your character's physical and mental traits here.": "Опишите ментальные и физические черты персонажа здесь", + "This will be the first message from the character that starts every chat.": "Это булет первое сообщение от Персонажа при начале нового чата", + "Chat Name (Optional)": "Название Чата (Необязательно)", + "Filter...": "Отфильтровать...", + "Search...": "Поиск...", + "Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Все содержание этой ячейки будет заменять стандартный Промт", + "Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Все содержание этой ячейки будет заменять стандартный Джейлбрейк", + "(Botmaker's name / Contact Info)": "Ваше имя / Контакты", + "(If you want to track character versions)": "Если вы хотите отслеживать модель персонажа", + "(Describe the bot, give use tips, or list the chat models it has been tested on. This will be displayed in the character list.)": "Опишите Персонажа, дайте советы, или упомяните на каких моделях он был протестирован", + "(Write a comma-separated list of tags)": "Запишите лист тэгов, разделяя запятой", + "(A brief description of the personality)": "Краткое описание личности)", + "(Circumstances and context of the interaction)": "(Обстоятельства и контекст этого взаимодействия)", + "(Examples of chat dialog. Begin each example with START on a new line.)": "(Примеры диалога. Начинайте каждый пример с START или новой линией.)", + "Injection text (supports parameters)": "Текст включения (Поддерживает параметры)", + "Injection depth": "Глубина включения", + "Type here...": "Пишите здесь...", + "Comma separated (required)": "Разделено запятыми (Обязательно)", + "Comma separated (ignored if empty)": "Разделено запятыми (Игнорируется если пусто)", + "What this keyword should mean to the AI, sent verbatim": "Значение этого ключевого слова, отправляется ИИ дословно", + "Not sent to the AI": "Не отправляется ИИ", + "(This will be the first message from the character that starts every chat)": "(Это будет первое сообщение от персонажа, когда вы начинаете новый чат)", + "Not connected to API!": "Нет подключения к API", + "AI Response Configuration": "Еастройка Ответа ИИ", + "AI Configuration panel will stay open": "Панель Настройки ИИ останется открытой", + "Update current preset": "Обновить текущую настройку", + "Create new preset": "Создать новую настройку", + "Import preset": "Внести настройку", + "Export preset": "Скачать настройку", + "Delete the preset": "Удалить настройку", + "NSFW block goes first in the resulting prompt": "НСФВ блокировка идет первой при отправки Промта", + "Enables OpenAI completion streaming": "Включить процесс генерации OpenAI", + "Wrap user messages in quotes before sending": "Заключить ответ Пользователя в кавычки", + "Restore default prompt": "Восстановить станндартный промт", + "New preset": "Новая настройка", + "Delete preset": "Удалить настройку", + "Restore default jailbreak": "Восстановить стандартный Джейлбрейк", + "Restore default reply": "Восстановить стандартный ответ", + "Restore defaul note": "Восстановить стандартную заметку", + "API Connections": "Соединения API", + "Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь с плохими ответами ставя в очередь только подтвержденных работников. Может замедлить время ответа.", + "Clear your API key": "Очистить свои ключи API", + "Refresh models": "Обновить модели", + "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai", + "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Подверждает ваше соединение к API. Знайте, что за это снимут деньги с вашего счета.", + "Create New": "Создать новое", + "Edit": "Изменить", + "World Info & Soft Prompts": "Информация о Мире & Мягкий Промт", + "Locked = World Editor will stay open": "Закреплено = Редактирование Мира останется открытым", + "Entries can activate other entries by mentioning their keywords": "Записи могут активировать другие записи если в них содержаться ключевые слова", + "Lookup for the entry keys in the context will respect the case": "Большая буква имеет значение при активации ключевого слова", + "If the entry key consists of only one word, it would not be matched as part of other words": "Если ключевое слово состоит только из одного слова, оно не будет активироваться как часть других слов", + "Open all Entries": "Открыть все Записи", + "Close all Entries": "Закрыть все Записи", + "Create": "Создать", + "Import World Info": "Внести Информацию Мира", + "Export World Info": "Скачать Информацию Мира", + "Delete World Info": "Удалить Информацию Мира", + "Rename World Info": "Переименовать Информацию Мира", + "Save changes to a new theme file": "Сохранить изменения в новой теме", + "removes blur and uses alternative background color for divs": "убирает размытие и использует альтернативный фон для разделов", + "If checked and the character card contains a prompt override (System Prompt), use that instead.": "Если выбрано и карточка персонажа содержит собственный промт (Системный Промт), выберите это", + "If checked and the character card contains a jailbreak override (Post History Instruction), use that instead.": "Если выбрано и карточка персонажа содержит собственный Джейлбрейк (После Истории Инструкций), выберите это", + "AI Response Formatting": "Формат ответа ИИ", + "Change Background Image": "Изменить фон", + "Extensions": "Расширения", + "Click to set a new User Name": "Нажмите что бы выбрать новое имя Личности", + "Click to lock your selected persona to the current chat. Click again to remove the lock.": "Нажмите что бы закрепить выьранную личность к текущему чату", + "Click to set user name for all messages": "Нажмите что бы закрепить Личность для всех сообщений", + "Create a dummy persona": "Создать болванку", + "Character Management": "Управление Персонажами", + "Locked = Character Management panel will stay open": "Закреплено = Панель Управление Персонажами останется открытой ", + "Select/Create Characters": "Выбрать/Создать персонажа", + "Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным и используется только для примера", + "Click to select a new avatar for this character": "Нажмите что бы выбрать новый аватар для этого персонажа", + "Add to Favorites": "Добавить в Любимые", + "Advanced Definition": "Расширенные Определения", + "Character Lore": "Сведения Персонажа", + "Export and Download": "Экспортировать и Скачать", + "Duplicate Character": "Дублировать персонажа", + "Create Character": "Создать персонажа", + "Delete Character": "Удалить персонажа", + "View all tags": "Показать все тэги", + "Click to set additional greeting messages": "Нажмите что бы создать дополнительное вступительное сообщение", + "Show / Hide Description and First Message": "Показать/Убрать Описание и Первое сообщение", + "Click to select a new avatar for this group": "Нажмите что бы выбрать новый аватар для этого группового чата", + "Set a group chat scenario": "Создать сценарий для группового чата", + "Restore collage avatar": "Восстановить коллаж персонажа", + "Create New Character": "Создать нового персонажа", + "Import Character from File": "Внести персонажа из файла", + "Import content from external URL": "Вставить содержимое из внешнего URL", + "Create New Chat Group": "Создать новый групповой чат", + "Characters sorting order": "Упорядовачивание порядка персонажа", + "Add chat injection": "Добавить включение в чат", + "Remove injection": "Убрать включение", + "Remove": "Убрать", + "Select a World Info file for": "Выбрать файл для Информации Мира", + "Primary Lorebook": "Основной Лорбук", + "A selected World Info will be bound to this character as its own Lorebook.": "Информация Мира будет закреплена на персонажем как собственный Лорбук", + "When generating an AI reply, it will be combined with the entries from a global World Info selector.": "Когда ИИ генерирует ответ, в него будет включены заметки из Информации Мира", + "Exporting a character would also export the selected Lorebook file embedded in the JSON data.": "Скачивание этого персонажа так же будет включать в себя закрепленный за ним Сведения", + "Additional Lorebooks": "Вспомогательные Сведения", + "Associate one or more auxillary Lorebooks with this character.": "Закрепить этот или еще больше вспомогательных Сведений за этим персонажем", + "NOTE: These choices are optional and won't be preserved on character export!": "ВНИМАНИЕ: Эти выборы необязательные и не будут сохранены при экспорте персонажа", + "Rename chat file": "Переименовать чат", + "Export JSONL chat file": "Создать чат в форме JSONL", + "Download chat as plain text document": "Скачать чат в формте .txt", + "Delete chat file": "Удалить файл этого чата", + "Delete tag": "Удалить тэг", + "Translate message": "Перевести сообщение", + "Generate Image": "Создать изображение", + "Narrate": "Повествовать", + "Prompt": "Промт", + "Create Bookmark": "Создать закладку", + "Copy": "Скопировать", + "Open bookmark chat": "Открыть чат из закладки", + "Confirm": "Подтвердить", + "Copy this message": "Скопировать это сообщение", + "Delete this message": "Удалить это сообщение", + "Move message up": "Переместить сообщение вверх", + "Move message down": "Переместить сообщение вниз", + "Enlarge": "Увеличить", + "Temporarily disable automatic replies from this character": "Временно отключить сообщения от этого персонажа", + "Enable automatic replies from this character": "Включить автоматическую отправку сообщения этого персонажа", + "Trigger a message from this character": "Активировать сообщение этого персонажа", + "Move up": "Переместить вверх", + "Move down": "Переместить вниз", + "View character card": "Посмотреть карточку персонажа", + "Remove from group": "Убрать из группы", + "Add to group": "Добавить в группу", + "Add": "Добавить", + "Abort request": "Прекратить генерацию", + "Send a message": "отправить сообщение", + "Ask AI to write your message for you": "ИИ напишет сообщение за вас", + "Continue the last message": "Продолжить текущее сообщение", + "Bind user name to that avatar": "Закрепить имя за этой личностью", + "Select this as default persona for the new chats.": "Выбрать эту как стартовую личность", + "Change persona image": "Сменить изображение личности", + "Delete persona": "Удалить личность" } } diff --git a/public/img/ai21.svg b/public/img/ai21.svg new file mode 100644 index 000000000..891beda5a --- /dev/null +++ b/public/img/ai21.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/public/index.html b/public/index.html index 46f1f58c5..0d73bd76a 100644 --- a/public/index.html +++ b/public/index.html @@ -22,31 +22,45 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + - SillyTavern @@ -130,13 +143,12 @@
- @@ -195,10 +209,11 @@
+ - - + +
@@ -654,7 +669,7 @@ Max prompt cost: Unknown
-
+
Temperature
@@ -669,7 +684,7 @@
-
+
Frequency Penalty
@@ -684,7 +699,7 @@
-
+
Presence Penalty
@@ -699,7 +714,22 @@
-
+
+
+ Count Penalty +
+
+
+ +
+
+
+ select +
+
+
+
+
Top K
@@ -714,7 +744,7 @@
-
+
Top P
@@ -1418,6 +1448,22 @@ Helps the model to associate messages in group chats. Names must only contain letters or numbers without whitespaces.
+
+ +
+ Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's. +
+
+
+ +
+ Exclude the assistant suffix from being added to the end of prompt (Requires jailbreak with 'Assistant:' in it). +
+
Quick Edit @@ -1429,8 +1475,8 @@ Select a character to show quick edit options.
-
- Assistant Prefill +
+ Assistant Prefill
@@ -1577,8 +1623,8 @@ - - + + @@ -1618,7 +1664,7 @@ - +
@@ -1761,22 +1807,27 @@
-
-
+ +

Blocking API url

- Example: http://127.0.0.1:5000/ + Example: http://127.0.0.1:5000/api
@@ -1804,6 +1855,7 @@ +

OpenAI API key

@@ -1972,6 +2024,27 @@
+
+

AI21 API Key

+
+ + +
+
+ For privacy reasons, your API key will be hidden after you reload the page. +
+
+

AI21 Model

+ +
+
+
@@ -2015,14 +2088,14 @@
-
- +
@@ -3238,7 +3332,7 @@
- + @@ -3267,16 +3361,6 @@
- - -
-
-
-
- -
-
-
@@ -3403,8 +3487,9 @@
+
- +

@@ -3645,9 +3730,7 @@ Content - (Tokens:  - 0 - ) + (Tokens:  counting...) diff --git a/public/instruct/Alpaca.json b/public/instruct/Alpaca.json index bec643c96..57d54cfb3 100644 --- a/public/instruct/Alpaca.json +++ b/public/instruct/Alpaca.json @@ -1,11 +1,15 @@ { "name": "Alpaca", "system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n", - "system_sequence": "", - "stop_sequence": "", "input_sequence": "### Instruction:", "output_sequence": "### Response:", "last_output_sequence": "", + "system_sequence": "", + "stop_sequence": "", "separator_sequence": "", - "wrap": true + "wrap": true, + "macro": true, + "names": false, + "names_force_groups": true, + "activation_regex": "" } diff --git a/public/instruct/Koala.json b/public/instruct/Koala.json index 3a1f08887..0b6a85f1b 100644 --- a/public/instruct/Koala.json +++ b/public/instruct/Koala.json @@ -1,11 +1,15 @@ { "name": "Koala", "system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n", - "system_sequence": "BEGINNING OF CONVERSATION: ", - "stop_sequence": "", "input_sequence": "USER: ", "output_sequence": "GPT: ", "last_output_sequence": "", + "system_sequence": "BEGINNING OF CONVERSATION: ", + "stop_sequence": "", "separator_sequence": "", - "wrap": false + "wrap": false, + "macro": true, + "names": false, + "names_force_groups": true, + "activation_regex": "" } diff --git a/public/instruct/Llama2.json b/public/instruct/Llama2.json index 1ce4c71e0..5d02caf1f 100644 --- a/public/instruct/Llama2.json +++ b/public/instruct/Llama2.json @@ -1,11 +1,15 @@ { "name": "Llama 2", - "system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.\n<>\n", + "system_prompt": "[INST] <>\nWrite {{char}}'s next reply in this fictional roleplay with {{user}}.\n<>\n", + "input_sequence": "[INST] ", + "output_sequence": " [/INST] ", + "last_output_sequence": "", "system_sequence": "[INST] <>\n", "stop_sequence": "", - "input_sequence": "[INST]", - "output_sequence": "[/INST]", - "last_output_sequence": "", "separator_sequence": "\n", - "wrap": false + "wrap": false, + "macro": true, + "names": false, + "names_force_groups": true, + "activation_regex": "" } diff --git a/public/instruct/Metharme.json b/public/instruct/Metharme.json index 96bb68a96..c9e4e14ef 100644 --- a/public/instruct/Metharme.json +++ b/public/instruct/Metharme.json @@ -1,11 +1,15 @@ { "name": "Metharme", "system_prompt": "Enter roleplay mode. You must act as {{char}}, whose persona follows:", - "system_sequence": "<|system|>", - "stop_sequence": "", "input_sequence": "<|user|>", "output_sequence": "<|model|>", "last_output_sequence": "", + "system_sequence": "<|system|>", + "stop_sequence": "", "separator_sequence": "", - "wrap": false + "wrap": false, + "macro": true, + "names": false, + "names_force_groups": true, + "activation_regex": "" } diff --git a/public/instruct/OpenOrca-OpenChat.json b/public/instruct/OpenOrca-OpenChat.json index 2b27b4fa3..7c0934075 100644 --- a/public/instruct/OpenOrca-OpenChat.json +++ b/public/instruct/OpenOrca-OpenChat.json @@ -1,13 +1,15 @@ { - "input_sequence": "User: ", - "macro": true, "name": "OpenOrca/OpenChat", - "names": true, + "system_prompt": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate.\n", + "input_sequence": "User: ", "output_sequence": "<|end_of_turn|>\nAssistant: ", "last_output_sequence": "", - "separator_sequence": "<|end_of_turn|>\n", - "stop_sequence": "", - "system_prompt": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate.\n", "system_sequence": "", - "wrap": false + "stop_sequence": "", + "separator_sequence": "<|end_of_turn|>\n", + "wrap": false, + "macro": true, + "names": false, + "names_force_groups": true, + "activation_regex": "" } diff --git a/public/instruct/Roleplay.json b/public/instruct/Roleplay.json index 46c4d4aee..c63ec13af 100644 --- a/public/instruct/Roleplay.json +++ b/public/instruct/Roleplay.json @@ -1,13 +1,15 @@ { - "input_sequence": "### Instruction:", - "macro": true, "name": "Roleplay", - "names": true, - "last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):", - "output_sequence": "### Response:", - "separator_sequence": "", - "stop_sequence": "", - "system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n\n### Input:", + "system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n", + "input_sequence": "\n### Instruction:", + "output_sequence": "\n### Response:", + "last_output_sequence": "\n### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):", "system_sequence": "", - "wrap": true + "stop_sequence": "", + "separator_sequence": "", + "wrap": true, + "macro": true, + "names": true, + "names_force_groups": true, + "activation_regex": "" } diff --git a/public/instruct/Vicuna 1.0.json b/public/instruct/Vicuna 1.0.json index a5ac33684..2674380dd 100644 --- a/public/instruct/Vicuna 1.0.json +++ b/public/instruct/Vicuna 1.0.json @@ -1,11 +1,15 @@ { "name": "Vicuna 1.0", "system_prompt": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n", - "system_sequence": "", - "stop_sequence": "", "input_sequence": "### Human:", "output_sequence": "### Assistant:", "last_output_sequence": "", + "system_sequence": "", + "stop_sequence": "", "separator_sequence": "", - "wrap": true + "wrap": true, + "macro": true, + "names": false, + "names_force_groups": true, + "activation_regex": "" } diff --git a/public/instruct/Vicuna 1.1.json b/public/instruct/Vicuna 1.1.json index 52cf600ac..e3e751ff7 100644 --- a/public/instruct/Vicuna 1.1.json +++ b/public/instruct/Vicuna 1.1.json @@ -1,11 +1,15 @@ { "name": "Vicuna 1.1", "system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n", - "system_sequence": "BEGINNING OF CONVERSATION:", - "stop_sequence": "", "input_sequence": "USER: ", "output_sequence": "ASSISTANT: ", "last_output_sequence": "", + "system_sequence": "BEGINNING OF CONVERSATION:", + "stop_sequence": "", "separator_sequence": "", - "wrap": false + "wrap": false, + "macro": true, + "names": false, + "names_force_groups": true, + "activation_regex": "" } diff --git a/public/instruct/WizardLM-13B.json b/public/instruct/WizardLM-13B.json index 3b8b6d7cf..b26625d61 100644 --- a/public/instruct/WizardLM-13B.json +++ b/public/instruct/WizardLM-13B.json @@ -1,11 +1,15 @@ { "name": "WizardLM-13B", "system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next detailed reply in a fictional roleplay chat between {{user}} and {{char}}.", - "system_sequence": "", - "stop_sequence": "", "input_sequence": "USER: ", "output_sequence": "ASSISTANT: ", "last_output_sequence": "", + "system_sequence": "", + "stop_sequence": "", "separator_sequence": "", - "wrap": true + "wrap": true, + "macro": true, + "names": false, + "names_force_groups": true, + "activation_regex": "" } diff --git a/public/instruct/WizardLM.json b/public/instruct/WizardLM.json index c81aef3a2..ec384d061 100644 --- a/public/instruct/WizardLM.json +++ b/public/instruct/WizardLM.json @@ -1,11 +1,15 @@ { "name": "WizardLM", "system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n", - "system_sequence": "", - "stop_sequence": "", "input_sequence": "", "output_sequence": "### Response:", "last_output_sequence": "", + "system_sequence": "", + "stop_sequence": "", "separator_sequence": "", - "wrap": true + "wrap": true, + "macro": true, + "names": false, + "names_force_groups": true, + "activation_regex": "" } diff --git a/public/instruct/simple-proxy-for-tavern.json b/public/instruct/simple-proxy-for-tavern.json new file mode 100644 index 000000000..7fa224063 --- /dev/null +++ b/public/instruct/simple-proxy-for-tavern.json @@ -0,0 +1,15 @@ +{ + "name": "simple-proxy-for-tavern", + "system_prompt": "[System note: Write one reply only. Do not decide what {{user}} says or does. Write at least one paragraph, up to four. Be descriptive and immersive, providing vivid details about {{char}}'s actions, emotions, and the environment. Write with a high degree of complexity and burstiness. Do not repeat this message.]", + "input_sequence": "### Instruction:\n#### {{user}}:", + "output_sequence": "### Response:\n#### {{char}}:", + "last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):\n#### {{char}}:", + "system_sequence": "", + "stop_sequence": "", + "separator_sequence": "", + "wrap": true, + "macro": true, + "names": false, + "names_force_groups": false, + "activation_regex": "" +} diff --git a/public/scripts/cropper.min.js b/public/lib/cropper.min.js similarity index 100% rename from public/scripts/cropper.min.js rename to public/lib/cropper.min.js diff --git a/public/scripts/droll.js b/public/lib/droll.js similarity index 100% rename from public/scripts/droll.js rename to public/lib/droll.js diff --git a/public/scripts/eventemitter.js b/public/lib/eventemitter.js similarity index 100% rename from public/scripts/eventemitter.js rename to public/lib/eventemitter.js diff --git a/public/scripts/fuse.js b/public/lib/fuse.js similarity index 100% rename from public/scripts/fuse.js rename to public/lib/fuse.js diff --git a/public/scripts/gpt-2-3-tokenizer/README.md b/public/lib/gpt-2-3-tokenizer/README.md similarity index 100% rename from public/scripts/gpt-2-3-tokenizer/README.md rename to public/lib/gpt-2-3-tokenizer/README.md diff --git a/public/scripts/gpt-2-3-tokenizer/encoder.js b/public/lib/gpt-2-3-tokenizer/encoder.js similarity index 100% rename from public/scripts/gpt-2-3-tokenizer/encoder.js rename to public/lib/gpt-2-3-tokenizer/encoder.js diff --git a/public/scripts/gpt-2-3-tokenizer/mod.js b/public/lib/gpt-2-3-tokenizer/mod.js similarity index 100% rename from public/scripts/gpt-2-3-tokenizer/mod.js rename to public/lib/gpt-2-3-tokenizer/mod.js diff --git a/public/scripts/gpt-2-3-tokenizer/vocab.bpe.js b/public/lib/gpt-2-3-tokenizer/vocab.bpe.js similarity index 100% rename from public/scripts/gpt-2-3-tokenizer/vocab.bpe.js rename to public/lib/gpt-2-3-tokenizer/vocab.bpe.js diff --git a/public/scripts/gpt-3-tokenizer/array-keyed-map.js b/public/lib/gpt-3-tokenizer/array-keyed-map.js similarity index 100% rename from public/scripts/gpt-3-tokenizer/array-keyed-map.js rename to public/lib/gpt-3-tokenizer/array-keyed-map.js diff --git a/public/scripts/gpt-3-tokenizer/gpt3-tokenizer.js b/public/lib/gpt-3-tokenizer/gpt3-tokenizer.js similarity index 100% rename from public/scripts/gpt-3-tokenizer/gpt3-tokenizer.js rename to public/lib/gpt-3-tokenizer/gpt3-tokenizer.js diff --git a/public/scripts/gpt-3-tokenizer/gpt3-tokenizer.js.map b/public/lib/gpt-3-tokenizer/gpt3-tokenizer.js.map similarity index 100% rename from public/scripts/gpt-3-tokenizer/gpt3-tokenizer.js.map rename to public/lib/gpt-3-tokenizer/gpt3-tokenizer.js.map diff --git a/public/scripts/handlebars.js b/public/lib/handlebars.js similarity index 100% rename from public/scripts/handlebars.js rename to public/lib/handlebars.js diff --git a/public/scripts/highlight.min.js b/public/lib/highlight.min.js similarity index 100% rename from public/scripts/highlight.min.js rename to public/lib/highlight.min.js diff --git a/public/scripts/jquery-3.5.1.min.js b/public/lib/jquery-3.5.1.min.js similarity index 100% rename from public/scripts/jquery-3.5.1.min.js rename to public/lib/jquery-3.5.1.min.js diff --git a/public/scripts/jquery-cookie-1.4.1.min.js b/public/lib/jquery-cookie-1.4.1.min.js similarity index 100% rename from public/scripts/jquery-cookie-1.4.1.min.js rename to public/lib/jquery-cookie-1.4.1.min.js diff --git a/public/scripts/jquery-cropper.min.js b/public/lib/jquery-cropper.min.js similarity index 100% rename from public/scripts/jquery-cropper.min.js rename to public/lib/jquery-cropper.min.js diff --git a/public/scripts/jquery-ui.min.js b/public/lib/jquery-ui.min.js similarity index 100% rename from public/scripts/jquery-ui.min.js rename to public/lib/jquery-ui.min.js diff --git a/public/scripts/jquery.transit.min.js b/public/lib/jquery.transit.min.js similarity index 100% rename from public/scripts/jquery.transit.min.js rename to public/lib/jquery.transit.min.js diff --git a/public/scripts/jquery.ui.touch-punch.min.js b/public/lib/jquery.ui.touch-punch.min.js similarity index 100% rename from public/scripts/jquery.ui.touch-punch.min.js rename to public/lib/jquery.ui.touch-punch.min.js diff --git a/public/scripts/localforage.min.js b/public/lib/localforage.min.js similarity index 100% rename from public/scripts/localforage.min.js rename to public/lib/localforage.min.js diff --git a/public/scripts/moment.min.js b/public/lib/moment.min.js similarity index 100% rename from public/scripts/moment.min.js rename to public/lib/moment.min.js diff --git a/public/scripts/moment.min.js.map b/public/lib/moment.min.js.map similarity index 100% rename from public/scripts/moment.min.js.map rename to public/lib/moment.min.js.map diff --git a/public/scripts/pagination.js b/public/lib/pagination.js similarity index 100% rename from public/scripts/pagination.js rename to public/lib/pagination.js diff --git a/public/scripts/popper.js b/public/lib/popper.js similarity index 100% rename from public/scripts/popper.js rename to public/lib/popper.js diff --git a/public/scripts/popper.js.map b/public/lib/popper.js.map similarity index 100% rename from public/scripts/popper.js.map rename to public/lib/popper.js.map diff --git a/public/scripts/purify.min.js b/public/lib/purify.min.js similarity index 100% rename from public/scripts/purify.min.js rename to public/lib/purify.min.js diff --git a/public/scripts/purify.min.js.map b/public/lib/purify.min.js.map similarity index 100% rename from public/scripts/purify.min.js.map rename to public/lib/purify.min.js.map diff --git a/public/scripts/seedrandom.min.js b/public/lib/seedrandom.min.js similarity index 100% rename from public/scripts/seedrandom.min.js rename to public/lib/seedrandom.min.js diff --git a/public/scripts/select2.min.js b/public/lib/select2.min.js similarity index 100% rename from public/scripts/select2.min.js rename to public/lib/select2.min.js diff --git a/public/scripts/showdown-katex.min.js b/public/lib/showdown-katex.min.js similarity index 100% rename from public/scripts/showdown-katex.min.js rename to public/lib/showdown-katex.min.js diff --git a/public/scripts/showdown-katex.min.js.map b/public/lib/showdown-katex.min.js.map similarity index 100% rename from public/scripts/showdown-katex.min.js.map rename to public/lib/showdown-katex.min.js.map diff --git a/public/scripts/showdown-toc.min.js b/public/lib/showdown-toc.min.js similarity index 100% rename from public/scripts/showdown-toc.min.js rename to public/lib/showdown-toc.min.js diff --git a/public/scripts/showdown.min.js b/public/lib/showdown.min.js similarity index 99% rename from public/scripts/showdown.min.js rename to public/lib/showdown.min.js index fddb5a412..cf721d9f0 100644 --- a/public/scripts/showdown.min.js +++ b/public/lib/showdown.min.js @@ -1,3 +1,3 @@ /*! showdown v 2.1.0 - 21-04-2022 */ !function(){function a(e){"use strict";var r={omitExtraWLInCodeBlocks:{defaultValue:!1,describe:"Omit the default extra whiteline added to code blocks",type:"boolean"},noHeaderId:{defaultValue:!1,describe:"Turn on/off generated header id",type:"boolean"},prefixHeaderId:{defaultValue:!1,describe:"Add a prefix to the generated header ids. Passing a string will prefix that string to the header id. Setting to true will add a generic 'section-' prefix",type:"string"},rawPrefixHeaderId:{defaultValue:!1,describe:'Setting this option to true will prevent showdown from modifying the prefix. This might result in malformed IDs (if, for instance, the " char is used in the prefix)',type:"boolean"},ghCompatibleHeaderId:{defaultValue:!1,describe:"Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)",type:"boolean"},rawHeaderId:{defaultValue:!1,describe:"Remove only spaces, ' and \" from generated header ids (including prefixes), replacing them with dashes (-). WARNING: This might result in malformed ids",type:"boolean"},headerLevelStart:{defaultValue:!1,describe:"The header blocks level start",type:"integer"},parseImgDimensions:{defaultValue:!1,describe:"Turn on/off image dimension parsing",type:"boolean"},simplifiedAutoLink:{defaultValue:!1,describe:"Turn on/off GFM autolink style",type:"boolean"},excludeTrailingPunctuationFromURLs:{defaultValue:!1,describe:"Excludes trailing punctuation from links generated with autoLinking",type:"boolean"},literalMidWordUnderscores:{defaultValue:!1,describe:"Parse midword underscores as literal underscores",type:"boolean"},literalMidWordAsterisks:{defaultValue:!1,describe:"Parse midword asterisks as literal asterisks",type:"boolean"},strikethrough:{defaultValue:!1,describe:"Turn on/off strikethrough support",type:"boolean"},tables:{defaultValue:!1,describe:"Turn on/off tables support",type:"boolean"},tablesHeaderId:{defaultValue:!1,describe:"Add an id to table headers",type:"boolean"},ghCodeBlocks:{defaultValue:!0,describe:"Turn on/off GFM fenced code blocks support",type:"boolean"},tasklists:{defaultValue:!1,describe:"Turn on/off GFM tasklist support",type:"boolean"},smoothLivePreview:{defaultValue:!1,describe:"Prevents weird effects in live previews due to incomplete input",type:"boolean"},smartIndentationFix:{defaultValue:!1,describe:"Tries to smartly fix indentation in es6 strings",type:"boolean"},disableForced4SpacesIndentedSublists:{defaultValue:!1,describe:"Disables the requirement of indenting nested sublists by 4 spaces",type:"boolean"},simpleLineBreaks:{defaultValue:!1,describe:"Parses simple line breaks as
(GFM Style)",type:"boolean"},requireSpaceBeforeHeadingText:{defaultValue:!1,describe:"Makes adding a space between `#` and the header text mandatory (GFM Style)",type:"boolean"},ghMentions:{defaultValue:!1,describe:"Enables github @mentions",type:"boolean"},ghMentionsLink:{defaultValue:"https://github.com/{u}",describe:"Changes the link generated by @mentions. Only applies if ghMentions option is enabled.",type:"string"},encodeEmails:{defaultValue:!0,describe:"Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities",type:"boolean"},openLinksInNewWindow:{defaultValue:!1,describe:"Open all links in new windows",type:"boolean"},backslashEscapesHTMLTags:{defaultValue:!1,describe:"Support for HTML Tag escaping. ex:
foo
",type:"boolean"},emoji:{defaultValue:!1,describe:"Enable emoji support. Ex: `this is a :smile: emoji`",type:"boolean"},underline:{defaultValue:!1,describe:"Enable support for underline. Syntax is double or triple underscores: `__underline word__`. With this option enabled, underscores no longer parses into `` and ``",type:"boolean"},ellipsis:{defaultValue:!0,describe:"Replaces three dots with the ellipsis unicode character",type:"boolean"},completeHTMLDocument:{defaultValue:!1,describe:"Outputs a complete html document, including ``, `` and `` tags",type:"boolean"},metadata:{defaultValue:!1,describe:"Enable support for document metadata (defined at the top of the document between `«««` and `»»»` or between `---` and `---`).",type:"boolean"},splitAdjacentBlockquotes:{defaultValue:!1,describe:"Split adjacent blockquote blocks",type:"boolean"}};if(!1===e)return JSON.parse(JSON.stringify(r));var t,a={};for(t in r)r.hasOwnProperty(t)&&(a[t]=r[t].defaultValue);return a}var x={},t={},d={},p=a(!0),h="vanilla",_={github:{omitExtraWLInCodeBlocks:!0,simplifiedAutoLink:!0,excludeTrailingPunctuationFromURLs:!0,literalMidWordUnderscores:!0,strikethrough:!0,tables:!0,tablesHeaderId:!0,ghCodeBlocks:!0,tasklists:!0,disableForced4SpacesIndentedSublists:!0,simpleLineBreaks:!0,requireSpaceBeforeHeadingText:!0,ghCompatibleHeaderId:!0,ghMentions:!0,backslashEscapesHTMLTags:!0,emoji:!0,splitAdjacentBlockquotes:!0},original:{noHeaderId:!0,ghCodeBlocks:!1},ghost:{omitExtraWLInCodeBlocks:!0,parseImgDimensions:!0,simplifiedAutoLink:!0,excludeTrailingPunctuationFromURLs:!0,literalMidWordUnderscores:!0,strikethrough:!0,tables:!0,tablesHeaderId:!0,ghCodeBlocks:!0,tasklists:!0,smoothLivePreview:!0,simpleLineBreaks:!0,requireSpaceBeforeHeadingText:!0,ghMentions:!1,encodeEmails:!0},vanilla:a(!0),allOn:function(){"use strict";var e,r=a(!0),t={};for(e in r)r.hasOwnProperty(e)&&(t[e]=!0);return t}()};function g(e,r){"use strict";var t=r?"Error in "+r+" extension->":"Error in unnamed extension",a={valid:!0,error:""};x.helper.isArray(e)||(e=[e]);for(var n=0;n").replace(/&/g,"&")};function u(e,r,t,a){"use strict";var n,s,o,i=-1<(a=a||"").indexOf("g"),l=new RegExp(r+"|"+t,"g"+a.replace(/g/g,"")),c=new RegExp(r,a.replace(/g/g,"")),u=[];do{for(n=0;p=l.exec(e);)if(c.test(p[0]))n++||(o=(s=l.lastIndex)-p[0].length);else if(n&&!--n){var d=p.index+p[0].length,p={left:{start:o,end:s},match:{start:s,end:p.index},right:{start:p.index,end:d},wholeMatch:{start:o,end:d}};if(u.push(p),!i)return u}}while(n&&(l.lastIndex=s));return u}function s(u){"use strict";return function(e,r,t,a,n,s,o){var i=t=t.replace(x.helper.regexes.asteriskDashAndColon,x.helper.escapeCharactersCallback),l="",c="",r=r||"",o=o||"";return/^www\./i.test(t)&&(t=t.replace(/^www\./i,"http://www.")),u.excludeTrailingPunctuationFromURLs&&s&&(l=s),r+'"+i+""+l+o}}function o(n,s){"use strict";return function(e,r,t){var a="mailto:";return r=r||"",t=x.subParser("unescapeSpecialChars")(t,n,s),n.encodeEmails?(a=x.helper.encodeEmailAddress(a+t),t=x.helper.encodeEmailAddress(t)):a+=t,r+'
'+t+""}}x.helper.matchRecursiveRegExp=function(e,r,t,a){"use strict";for(var n=u(e,r,t,a),s=[],o=0;o>=0,t=String(t||" "),e.length>r?String(e):((r-=e.length)>t.length&&(t+=t.repeat(r/t.length)),String(e)+t.slice(0,r))},"undefined"==typeof console&&(console={warn:function(e){"use strict";alert(e)},log:function(e){"use strict";alert(e)},error:function(e){"use strict";throw e}}),x.helper.regexes={asteriskDashAndColon:/([*_:~])/g},x.helper.emojis={"+1":"👍","-1":"👎",100:"💯",1234:"🔢","1st_place_medal":"🥇","2nd_place_medal":"🥈","3rd_place_medal":"🥉","8ball":"🎱",a:"🅰️",ab:"🆎",abc:"🔤",abcd:"🔡",accept:"🉑",aerial_tramway:"🚡",airplane:"✈️",alarm_clock:"⏰",alembic:"⚗️",alien:"👽",ambulance:"🚑",amphora:"🏺",anchor:"⚓️",angel:"👼",anger:"💢",angry:"😠",anguished:"😧",ant:"🐜",apple:"🍎",aquarius:"♒️",aries:"♈️",arrow_backward:"◀️",arrow_double_down:"⏬",arrow_double_up:"⏫",arrow_down:"⬇️",arrow_down_small:"🔽",arrow_forward:"▶️",arrow_heading_down:"⤵️",arrow_heading_up:"⤴️",arrow_left:"⬅️",arrow_lower_left:"↙️",arrow_lower_right:"↘️",arrow_right:"➡️",arrow_right_hook:"↪️",arrow_up:"⬆️",arrow_up_down:"↕️",arrow_up_small:"🔼",arrow_upper_left:"↖️",arrow_upper_right:"↗️",arrows_clockwise:"🔃",arrows_counterclockwise:"🔄",art:"🎨",articulated_lorry:"🚛",artificial_satellite:"🛰",astonished:"😲",athletic_shoe:"👟",atm:"🏧",atom_symbol:"⚛️",avocado:"🥑",b:"🅱️",baby:"👶",baby_bottle:"🍼",baby_chick:"🐤",baby_symbol:"🚼",back:"🔙",bacon:"🥓",badminton:"🏸",baggage_claim:"🛄",baguette_bread:"🥖",balance_scale:"⚖️",balloon:"🎈",ballot_box:"🗳",ballot_box_with_check:"☑️",bamboo:"🎍",banana:"🍌",bangbang:"‼️",bank:"🏦",bar_chart:"📊",barber:"💈",baseball:"⚾️",basketball:"🏀",basketball_man:"⛹️",basketball_woman:"⛹️‍♀️",bat:"🦇",bath:"🛀",bathtub:"🛁",battery:"🔋",beach_umbrella:"🏖",bear:"🐻",bed:"🛏",bee:"🐝",beer:"🍺",beers:"🍻",beetle:"🐞",beginner:"🔰",bell:"🔔",bellhop_bell:"🛎",bento:"🍱",biking_man:"🚴",bike:"🚲",biking_woman:"🚴‍♀️",bikini:"👙",biohazard:"☣️",bird:"🐦",birthday:"🎂",black_circle:"⚫️",black_flag:"🏴",black_heart:"🖤",black_joker:"🃏",black_large_square:"⬛️",black_medium_small_square:"◾️",black_medium_square:"◼️",black_nib:"✒️",black_small_square:"▪️",black_square_button:"🔲",blonde_man:"👱",blonde_woman:"👱‍♀️",blossom:"🌼",blowfish:"🐡",blue_book:"📘",blue_car:"🚙",blue_heart:"💙",blush:"😊",boar:"🐗",boat:"⛵️",bomb:"💣",book:"📖",bookmark:"🔖",bookmark_tabs:"📑",books:"📚",boom:"💥",boot:"👢",bouquet:"💐",bowing_man:"🙇",bow_and_arrow:"🏹",bowing_woman:"🙇‍♀️",bowling:"🎳",boxing_glove:"🥊",boy:"👦",bread:"🍞",bride_with_veil:"👰",bridge_at_night:"🌉",briefcase:"💼",broken_heart:"💔",bug:"🐛",building_construction:"🏗",bulb:"💡",bullettrain_front:"🚅",bullettrain_side:"🚄",burrito:"🌯",bus:"🚌",business_suit_levitating:"🕴",busstop:"🚏",bust_in_silhouette:"👤",busts_in_silhouette:"👥",butterfly:"🦋",cactus:"🌵",cake:"🍰",calendar:"📆",call_me_hand:"🤙",calling:"📲",camel:"🐫",camera:"📷",camera_flash:"📸",camping:"🏕",cancer:"♋️",candle:"🕯",candy:"🍬",canoe:"🛶",capital_abcd:"🔠",capricorn:"♑️",car:"🚗",card_file_box:"🗃",card_index:"📇",card_index_dividers:"🗂",carousel_horse:"🎠",carrot:"🥕",cat:"🐱",cat2:"🐈",cd:"💿",chains:"⛓",champagne:"🍾",chart:"💹",chart_with_downwards_trend:"📉",chart_with_upwards_trend:"📈",checkered_flag:"🏁",cheese:"🧀",cherries:"🍒",cherry_blossom:"🌸",chestnut:"🌰",chicken:"🐔",children_crossing:"🚸",chipmunk:"🐿",chocolate_bar:"🍫",christmas_tree:"🎄",church:"⛪️",cinema:"🎦",circus_tent:"🎪",city_sunrise:"🌇",city_sunset:"🌆",cityscape:"🏙",cl:"🆑",clamp:"🗜",clap:"👏",clapper:"🎬",classical_building:"🏛",clinking_glasses:"🥂",clipboard:"📋",clock1:"🕐",clock10:"🕙",clock1030:"🕥",clock11:"🕚",clock1130:"🕦",clock12:"🕛",clock1230:"🕧",clock130:"🕜",clock2:"🕑",clock230:"🕝",clock3:"🕒",clock330:"🕞",clock4:"🕓",clock430:"🕟",clock5:"🕔",clock530:"🕠",clock6:"🕕",clock630:"🕡",clock7:"🕖",clock730:"🕢",clock8:"🕗",clock830:"🕣",clock9:"🕘",clock930:"🕤",closed_book:"📕",closed_lock_with_key:"🔐",closed_umbrella:"🌂",cloud:"☁️",cloud_with_lightning:"🌩",cloud_with_lightning_and_rain:"⛈",cloud_with_rain:"🌧",cloud_with_snow:"🌨",clown_face:"🤡",clubs:"♣️",cocktail:"🍸",coffee:"☕️",coffin:"⚰️",cold_sweat:"😰",comet:"☄️",computer:"💻",computer_mouse:"🖱",confetti_ball:"🎊",confounded:"😖",confused:"😕",congratulations:"㊗️",construction:"🚧",construction_worker_man:"👷",construction_worker_woman:"👷‍♀️",control_knobs:"🎛",convenience_store:"🏪",cookie:"🍪",cool:"🆒",policeman:"👮",copyright:"©️",corn:"🌽",couch_and_lamp:"🛋",couple:"👫",couple_with_heart_woman_man:"💑",couple_with_heart_man_man:"👨‍❤️‍👨",couple_with_heart_woman_woman:"👩‍❤️‍👩",couplekiss_man_man:"👨‍❤️‍💋‍👨",couplekiss_man_woman:"💏",couplekiss_woman_woman:"👩‍❤️‍💋‍👩",cow:"🐮",cow2:"🐄",cowboy_hat_face:"🤠",crab:"🦀",crayon:"🖍",credit_card:"💳",crescent_moon:"🌙",cricket:"🏏",crocodile:"🐊",croissant:"🥐",crossed_fingers:"🤞",crossed_flags:"🎌",crossed_swords:"⚔️",crown:"👑",cry:"😢",crying_cat_face:"😿",crystal_ball:"🔮",cucumber:"🥒",cupid:"💘",curly_loop:"➰",currency_exchange:"💱",curry:"🍛",custard:"🍮",customs:"🛃",cyclone:"🌀",dagger:"🗡",dancer:"💃",dancing_women:"👯",dancing_men:"👯‍♂️",dango:"🍡",dark_sunglasses:"🕶",dart:"🎯",dash:"💨",date:"📅",deciduous_tree:"🌳",deer:"🦌",department_store:"🏬",derelict_house:"🏚",desert:"🏜",desert_island:"🏝",desktop_computer:"🖥",male_detective:"🕵️",diamond_shape_with_a_dot_inside:"💠",diamonds:"♦️",disappointed:"😞",disappointed_relieved:"😥",dizzy:"💫",dizzy_face:"😵",do_not_litter:"🚯",dog:"🐶",dog2:"🐕",dollar:"💵",dolls:"🎎",dolphin:"🐬",door:"🚪",doughnut:"🍩",dove:"🕊",dragon:"🐉",dragon_face:"🐲",dress:"👗",dromedary_camel:"🐪",drooling_face:"🤤",droplet:"💧",drum:"🥁",duck:"🦆",dvd:"📀","e-mail":"📧",eagle:"🦅",ear:"👂",ear_of_rice:"🌾",earth_africa:"🌍",earth_americas:"🌎",earth_asia:"🌏",egg:"🥚",eggplant:"🍆",eight_pointed_black_star:"✴️",eight_spoked_asterisk:"✳️",electric_plug:"🔌",elephant:"🐘",email:"✉️",end:"🔚",envelope_with_arrow:"📩",euro:"💶",european_castle:"🏰",european_post_office:"🏤",evergreen_tree:"🌲",exclamation:"❗️",expressionless:"😑",eye:"👁",eye_speech_bubble:"👁‍🗨",eyeglasses:"👓",eyes:"👀",face_with_head_bandage:"🤕",face_with_thermometer:"🤒",fist_oncoming:"👊",factory:"🏭",fallen_leaf:"🍂",family_man_woman_boy:"👪",family_man_boy:"👨‍👦",family_man_boy_boy:"👨‍👦‍👦",family_man_girl:"👨‍👧",family_man_girl_boy:"👨‍👧‍👦",family_man_girl_girl:"👨‍👧‍👧",family_man_man_boy:"👨‍👨‍👦",family_man_man_boy_boy:"👨‍👨‍👦‍👦",family_man_man_girl:"👨‍👨‍👧",family_man_man_girl_boy:"👨‍👨‍👧‍👦",family_man_man_girl_girl:"👨‍👨‍👧‍👧",family_man_woman_boy_boy:"👨‍👩‍👦‍👦",family_man_woman_girl:"👨‍👩‍👧",family_man_woman_girl_boy:"👨‍👩‍👧‍👦",family_man_woman_girl_girl:"👨‍👩‍👧‍👧",family_woman_boy:"👩‍👦",family_woman_boy_boy:"👩‍👦‍👦",family_woman_girl:"👩‍👧",family_woman_girl_boy:"👩‍👧‍👦",family_woman_girl_girl:"👩‍👧‍👧",family_woman_woman_boy:"👩‍👩‍👦",family_woman_woman_boy_boy:"👩‍👩‍👦‍👦",family_woman_woman_girl:"👩‍👩‍👧",family_woman_woman_girl_boy:"👩‍👩‍👧‍👦",family_woman_woman_girl_girl:"👩‍👩‍👧‍👧",fast_forward:"⏩",fax:"📠",fearful:"😨",feet:"🐾",female_detective:"🕵️‍♀️",ferris_wheel:"🎡",ferry:"⛴",field_hockey:"🏑",file_cabinet:"🗄",file_folder:"📁",film_projector:"📽",film_strip:"🎞",fire:"🔥",fire_engine:"🚒",fireworks:"🎆",first_quarter_moon:"🌓",first_quarter_moon_with_face:"🌛",fish:"🐟",fish_cake:"🍥",fishing_pole_and_fish:"🎣",fist_raised:"✊",fist_left:"🤛",fist_right:"🤜",flags:"🎏",flashlight:"🔦",fleur_de_lis:"⚜️",flight_arrival:"🛬",flight_departure:"🛫",floppy_disk:"💾",flower_playing_cards:"🎴",flushed:"😳",fog:"🌫",foggy:"🌁",football:"🏈",footprints:"👣",fork_and_knife:"🍴",fountain:"⛲️",fountain_pen:"🖋",four_leaf_clover:"🍀",fox_face:"🦊",framed_picture:"🖼",free:"🆓",fried_egg:"🍳",fried_shrimp:"🍤",fries:"🍟",frog:"🐸",frowning:"😦",frowning_face:"☹️",frowning_man:"🙍‍♂️",frowning_woman:"🙍",middle_finger:"🖕",fuelpump:"⛽️",full_moon:"🌕",full_moon_with_face:"🌝",funeral_urn:"⚱️",game_die:"🎲",gear:"⚙️",gem:"💎",gemini:"♊️",ghost:"👻",gift:"🎁",gift_heart:"💝",girl:"👧",globe_with_meridians:"🌐",goal_net:"🥅",goat:"🐐",golf:"⛳️",golfing_man:"🏌️",golfing_woman:"🏌️‍♀️",gorilla:"🦍",grapes:"🍇",green_apple:"🍏",green_book:"📗",green_heart:"💚",green_salad:"🥗",grey_exclamation:"❕",grey_question:"❔",grimacing:"😬",grin:"😁",grinning:"😀",guardsman:"💂",guardswoman:"💂‍♀️",guitar:"🎸",gun:"🔫",haircut_woman:"💇",haircut_man:"💇‍♂️",hamburger:"🍔",hammer:"🔨",hammer_and_pick:"⚒",hammer_and_wrench:"🛠",hamster:"🐹",hand:"✋",handbag:"👜",handshake:"🤝",hankey:"💩",hatched_chick:"🐥",hatching_chick:"🐣",headphones:"🎧",hear_no_evil:"🙉",heart:"❤️",heart_decoration:"💟",heart_eyes:"😍",heart_eyes_cat:"😻",heartbeat:"💓",heartpulse:"💗",hearts:"♥️",heavy_check_mark:"✔️",heavy_division_sign:"➗",heavy_dollar_sign:"💲",heavy_heart_exclamation:"❣️",heavy_minus_sign:"➖",heavy_multiplication_x:"✖️",heavy_plus_sign:"➕",helicopter:"🚁",herb:"🌿",hibiscus:"🌺",high_brightness:"🔆",high_heel:"👠",hocho:"🔪",hole:"🕳",honey_pot:"🍯",horse:"🐴",horse_racing:"🏇",hospital:"🏥",hot_pepper:"🌶",hotdog:"🌭",hotel:"🏨",hotsprings:"♨️",hourglass:"⌛️",hourglass_flowing_sand:"⏳",house:"🏠",house_with_garden:"🏡",houses:"🏘",hugs:"🤗",hushed:"😯",ice_cream:"🍨",ice_hockey:"🏒",ice_skate:"⛸",icecream:"🍦",id:"🆔",ideograph_advantage:"🉐",imp:"👿",inbox_tray:"📥",incoming_envelope:"📨",tipping_hand_woman:"💁",information_source:"ℹ️",innocent:"😇",interrobang:"⁉️",iphone:"📱",izakaya_lantern:"🏮",jack_o_lantern:"🎃",japan:"🗾",japanese_castle:"🏯",japanese_goblin:"👺",japanese_ogre:"👹",jeans:"👖",joy:"😂",joy_cat:"😹",joystick:"🕹",kaaba:"🕋",key:"🔑",keyboard:"⌨️",keycap_ten:"🔟",kick_scooter:"🛴",kimono:"👘",kiss:"💋",kissing:"😗",kissing_cat:"😽",kissing_closed_eyes:"😚",kissing_heart:"😘",kissing_smiling_eyes:"😙",kiwi_fruit:"🥝",koala:"🐨",koko:"🈁",label:"🏷",large_blue_circle:"🔵",large_blue_diamond:"🔷",large_orange_diamond:"🔶",last_quarter_moon:"🌗",last_quarter_moon_with_face:"🌜",latin_cross:"✝️",laughing:"😆",leaves:"🍃",ledger:"📒",left_luggage:"🛅",left_right_arrow:"↔️",leftwards_arrow_with_hook:"↩️",lemon:"🍋",leo:"♌️",leopard:"🐆",level_slider:"🎚",libra:"♎️",light_rail:"🚈",link:"🔗",lion:"🦁",lips:"👄",lipstick:"💄",lizard:"🦎",lock:"🔒",lock_with_ink_pen:"🔏",lollipop:"🍭",loop:"➿",loud_sound:"🔊",loudspeaker:"📢",love_hotel:"🏩",love_letter:"💌",low_brightness:"🔅",lying_face:"🤥",m:"Ⓜ️",mag:"🔍",mag_right:"🔎",mahjong:"🀄️",mailbox:"📫",mailbox_closed:"📪",mailbox_with_mail:"📬",mailbox_with_no_mail:"📭",man:"👨",man_artist:"👨‍🎨",man_astronaut:"👨‍🚀",man_cartwheeling:"🤸‍♂️",man_cook:"👨‍🍳",man_dancing:"🕺",man_facepalming:"🤦‍♂️",man_factory_worker:"👨‍🏭",man_farmer:"👨‍🌾",man_firefighter:"👨‍🚒",man_health_worker:"👨‍⚕️",man_in_tuxedo:"🤵",man_judge:"👨‍⚖️",man_juggling:"🤹‍♂️",man_mechanic:"👨‍🔧",man_office_worker:"👨‍💼",man_pilot:"👨‍✈️",man_playing_handball:"🤾‍♂️",man_playing_water_polo:"🤽‍♂️",man_scientist:"👨‍🔬",man_shrugging:"🤷‍♂️",man_singer:"👨‍🎤",man_student:"👨‍🎓",man_teacher:"👨‍🏫",man_technologist:"👨‍💻",man_with_gua_pi_mao:"👲",man_with_turban:"👳",tangerine:"🍊",mans_shoe:"👞",mantelpiece_clock:"🕰",maple_leaf:"🍁",martial_arts_uniform:"🥋",mask:"😷",massage_woman:"💆",massage_man:"💆‍♂️",meat_on_bone:"🍖",medal_military:"🎖",medal_sports:"🏅",mega:"📣",melon:"🍈",memo:"📝",men_wrestling:"🤼‍♂️",menorah:"🕎",mens:"🚹",metal:"🤘",metro:"🚇",microphone:"🎤",microscope:"🔬",milk_glass:"🥛",milky_way:"🌌",minibus:"🚐",minidisc:"💽",mobile_phone_off:"📴",money_mouth_face:"🤑",money_with_wings:"💸",moneybag:"💰",monkey:"🐒",monkey_face:"🐵",monorail:"🚝",moon:"🌔",mortar_board:"🎓",mosque:"🕌",motor_boat:"🛥",motor_scooter:"🛵",motorcycle:"🏍",motorway:"🛣",mount_fuji:"🗻",mountain:"⛰",mountain_biking_man:"🚵",mountain_biking_woman:"🚵‍♀️",mountain_cableway:"🚠",mountain_railway:"🚞",mountain_snow:"🏔",mouse:"🐭",mouse2:"🐁",movie_camera:"🎥",moyai:"🗿",mrs_claus:"🤶",muscle:"💪",mushroom:"🍄",musical_keyboard:"🎹",musical_note:"🎵",musical_score:"🎼",mute:"🔇",nail_care:"💅",name_badge:"📛",national_park:"🏞",nauseated_face:"🤢",necktie:"👔",negative_squared_cross_mark:"❎",nerd_face:"🤓",neutral_face:"😐",new:"🆕",new_moon:"🌑",new_moon_with_face:"🌚",newspaper:"📰",newspaper_roll:"🗞",next_track_button:"⏭",ng:"🆖",no_good_man:"🙅‍♂️",no_good_woman:"🙅",night_with_stars:"🌃",no_bell:"🔕",no_bicycles:"🚳",no_entry:"⛔️",no_entry_sign:"🚫",no_mobile_phones:"📵",no_mouth:"😶",no_pedestrians:"🚷",no_smoking:"🚭","non-potable_water":"🚱",nose:"👃",notebook:"📓",notebook_with_decorative_cover:"📔",notes:"🎶",nut_and_bolt:"🔩",o:"⭕️",o2:"🅾️",ocean:"🌊",octopus:"🐙",oden:"🍢",office:"🏢",oil_drum:"🛢",ok:"🆗",ok_hand:"👌",ok_man:"🙆‍♂️",ok_woman:"🙆",old_key:"🗝",older_man:"👴",older_woman:"👵",om:"🕉",on:"🔛",oncoming_automobile:"🚘",oncoming_bus:"🚍",oncoming_police_car:"🚔",oncoming_taxi:"🚖",open_file_folder:"📂",open_hands:"👐",open_mouth:"😮",open_umbrella:"☂️",ophiuchus:"⛎",orange_book:"📙",orthodox_cross:"☦️",outbox_tray:"📤",owl:"🦉",ox:"🐂",package:"📦",page_facing_up:"📄",page_with_curl:"📃",pager:"📟",paintbrush:"🖌",palm_tree:"🌴",pancakes:"🥞",panda_face:"🐼",paperclip:"📎",paperclips:"🖇",parasol_on_ground:"⛱",parking:"🅿️",part_alternation_mark:"〽️",partly_sunny:"⛅️",passenger_ship:"🛳",passport_control:"🛂",pause_button:"⏸",peace_symbol:"☮️",peach:"🍑",peanuts:"🥜",pear:"🍐",pen:"🖊",pencil2:"✏️",penguin:"🐧",pensive:"😔",performing_arts:"🎭",persevere:"😣",person_fencing:"🤺",pouting_woman:"🙎",phone:"☎️",pick:"⛏",pig:"🐷",pig2:"🐖",pig_nose:"🐽",pill:"💊",pineapple:"🍍",ping_pong:"🏓",pisces:"♓️",pizza:"🍕",place_of_worship:"🛐",plate_with_cutlery:"🍽",play_or_pause_button:"⏯",point_down:"👇",point_left:"👈",point_right:"👉",point_up:"☝️",point_up_2:"👆",police_car:"🚓",policewoman:"👮‍♀️",poodle:"🐩",popcorn:"🍿",post_office:"🏣",postal_horn:"📯",postbox:"📮",potable_water:"🚰",potato:"🥔",pouch:"👝",poultry_leg:"🍗",pound:"💷",rage:"😡",pouting_cat:"😾",pouting_man:"🙎‍♂️",pray:"🙏",prayer_beads:"📿",pregnant_woman:"🤰",previous_track_button:"⏮",prince:"🤴",princess:"👸",printer:"🖨",purple_heart:"💜",purse:"👛",pushpin:"📌",put_litter_in_its_place:"🚮",question:"❓",rabbit:"🐰",rabbit2:"🐇",racehorse:"🐎",racing_car:"🏎",radio:"📻",radio_button:"🔘",radioactive:"☢️",railway_car:"🚃",railway_track:"🛤",rainbow:"🌈",rainbow_flag:"🏳️‍🌈",raised_back_of_hand:"🤚",raised_hand_with_fingers_splayed:"🖐",raised_hands:"🙌",raising_hand_woman:"🙋",raising_hand_man:"🙋‍♂️",ram:"🐏",ramen:"🍜",rat:"🐀",record_button:"⏺",recycle:"♻️",red_circle:"🔴",registered:"®️",relaxed:"☺️",relieved:"😌",reminder_ribbon:"🎗",repeat:"🔁",repeat_one:"🔂",rescue_worker_helmet:"⛑",restroom:"🚻",revolving_hearts:"💞",rewind:"⏪",rhinoceros:"🦏",ribbon:"🎀",rice:"🍚",rice_ball:"🍙",rice_cracker:"🍘",rice_scene:"🎑",right_anger_bubble:"🗯",ring:"💍",robot:"🤖",rocket:"🚀",rofl:"🤣",roll_eyes:"🙄",roller_coaster:"🎢",rooster:"🐓",rose:"🌹",rosette:"🏵",rotating_light:"🚨",round_pushpin:"📍",rowing_man:"🚣",rowing_woman:"🚣‍♀️",rugby_football:"🏉",running_man:"🏃",running_shirt_with_sash:"🎽",running_woman:"🏃‍♀️",sa:"🈂️",sagittarius:"♐️",sake:"🍶",sandal:"👡",santa:"🎅",satellite:"📡",saxophone:"🎷",school:"🏫",school_satchel:"🎒",scissors:"✂️",scorpion:"🦂",scorpius:"♏️",scream:"😱",scream_cat:"🙀",scroll:"📜",seat:"💺",secret:"㊙️",see_no_evil:"🙈",seedling:"🌱",selfie:"🤳",shallow_pan_of_food:"🥘",shamrock:"☘️",shark:"🦈",shaved_ice:"🍧",sheep:"🐑",shell:"🐚",shield:"🛡",shinto_shrine:"⛩",ship:"🚢",shirt:"👕",shopping:"🛍",shopping_cart:"🛒",shower:"🚿",shrimp:"🦐",signal_strength:"📶",six_pointed_star:"🔯",ski:"🎿",skier:"⛷",skull:"💀",skull_and_crossbones:"☠️",sleeping:"😴",sleeping_bed:"🛌",sleepy:"😪",slightly_frowning_face:"🙁",slightly_smiling_face:"🙂",slot_machine:"🎰",small_airplane:"🛩",small_blue_diamond:"🔹",small_orange_diamond:"🔸",small_red_triangle:"🔺",small_red_triangle_down:"🔻",smile:"😄",smile_cat:"😸",smiley:"😃",smiley_cat:"😺",smiling_imp:"😈",smirk:"😏",smirk_cat:"😼",smoking:"🚬",snail:"🐌",snake:"🐍",sneezing_face:"🤧",snowboarder:"🏂",snowflake:"❄️",snowman:"⛄️",snowman_with_snow:"☃️",sob:"😭",soccer:"⚽️",soon:"🔜",sos:"🆘",sound:"🔉",space_invader:"👾",spades:"♠️",spaghetti:"🍝",sparkle:"❇️",sparkler:"🎇",sparkles:"✨",sparkling_heart:"💖",speak_no_evil:"🙊",speaker:"🔈",speaking_head:"🗣",speech_balloon:"💬",speedboat:"🚤",spider:"🕷",spider_web:"🕸",spiral_calendar:"🗓",spiral_notepad:"🗒",spoon:"🥄",squid:"🦑",stadium:"🏟",star:"⭐️",star2:"🌟",star_and_crescent:"☪️",star_of_david:"✡️",stars:"🌠",station:"🚉",statue_of_liberty:"🗽",steam_locomotive:"🚂",stew:"🍲",stop_button:"⏹",stop_sign:"🛑",stopwatch:"⏱",straight_ruler:"📏",strawberry:"🍓",stuck_out_tongue:"😛",stuck_out_tongue_closed_eyes:"😝",stuck_out_tongue_winking_eye:"😜",studio_microphone:"🎙",stuffed_flatbread:"🥙",sun_behind_large_cloud:"🌥",sun_behind_rain_cloud:"🌦",sun_behind_small_cloud:"🌤",sun_with_face:"🌞",sunflower:"🌻",sunglasses:"😎",sunny:"☀️",sunrise:"🌅",sunrise_over_mountains:"🌄",surfing_man:"🏄",surfing_woman:"🏄‍♀️",sushi:"🍣",suspension_railway:"🚟",sweat:"😓",sweat_drops:"💦",sweat_smile:"😅",sweet_potato:"🍠",swimming_man:"🏊",swimming_woman:"🏊‍♀️",symbols:"🔣",synagogue:"🕍",syringe:"💉",taco:"🌮",tada:"🎉",tanabata_tree:"🎋",taurus:"♉️",taxi:"🚕",tea:"🍵",telephone_receiver:"📞",telescope:"🔭",tennis:"🎾",tent:"⛺️",thermometer:"🌡",thinking:"🤔",thought_balloon:"💭",ticket:"🎫",tickets:"🎟",tiger:"🐯",tiger2:"🐅",timer_clock:"⏲",tipping_hand_man:"💁‍♂️",tired_face:"😫",tm:"™️",toilet:"🚽",tokyo_tower:"🗼",tomato:"🍅",tongue:"👅",top:"🔝",tophat:"🎩",tornado:"🌪",trackball:"🖲",tractor:"🚜",traffic_light:"🚥",train:"🚋",train2:"🚆",tram:"🚊",triangular_flag_on_post:"🚩",triangular_ruler:"📐",trident:"🔱",triumph:"😤",trolleybus:"🚎",trophy:"🏆",tropical_drink:"🍹",tropical_fish:"🐠",truck:"🚚",trumpet:"🎺",tulip:"🌷",tumbler_glass:"🥃",turkey:"🦃",turtle:"🐢",tv:"📺",twisted_rightwards_arrows:"🔀",two_hearts:"💕",two_men_holding_hands:"👬",two_women_holding_hands:"👭",u5272:"🈹",u5408:"🈴",u55b6:"🈺",u6307:"🈯️",u6708:"🈷️",u6709:"🈶",u6e80:"🈵",u7121:"🈚️",u7533:"🈸",u7981:"🈲",u7a7a:"🈳",umbrella:"☔️",unamused:"😒",underage:"🔞",unicorn:"🦄",unlock:"🔓",up:"🆙",upside_down_face:"🙃",v:"✌️",vertical_traffic_light:"🚦",vhs:"📼",vibration_mode:"📳",video_camera:"📹",video_game:"🎮",violin:"🎻",virgo:"♍️",volcano:"🌋",volleyball:"🏐",vs:"🆚",vulcan_salute:"🖖",walking_man:"🚶",walking_woman:"🚶‍♀️",waning_crescent_moon:"🌘",waning_gibbous_moon:"🌖",warning:"⚠️",wastebasket:"🗑",watch:"⌚️",water_buffalo:"🐃",watermelon:"🍉",wave:"👋",wavy_dash:"〰️",waxing_crescent_moon:"🌒",wc:"🚾",weary:"😩",wedding:"💒",weight_lifting_man:"🏋️",weight_lifting_woman:"🏋️‍♀️",whale:"🐳",whale2:"🐋",wheel_of_dharma:"☸️",wheelchair:"♿️",white_check_mark:"✅",white_circle:"⚪️",white_flag:"🏳️",white_flower:"💮",white_large_square:"⬜️",white_medium_small_square:"◽️",white_medium_square:"◻️",white_small_square:"▫️",white_square_button:"🔳",wilted_flower:"🥀",wind_chime:"🎐",wind_face:"🌬",wine_glass:"🍷",wink:"😉",wolf:"🐺",woman:"👩",woman_artist:"👩‍🎨",woman_astronaut:"👩‍🚀",woman_cartwheeling:"🤸‍♀️",woman_cook:"👩‍🍳",woman_facepalming:"🤦‍♀️",woman_factory_worker:"👩‍🏭",woman_farmer:"👩‍🌾",woman_firefighter:"👩‍🚒",woman_health_worker:"👩‍⚕️",woman_judge:"👩‍⚖️",woman_juggling:"🤹‍♀️",woman_mechanic:"👩‍🔧",woman_office_worker:"👩‍💼",woman_pilot:"👩‍✈️",woman_playing_handball:"🤾‍♀️",woman_playing_water_polo:"🤽‍♀️",woman_scientist:"👩‍🔬",woman_shrugging:"🤷‍♀️",woman_singer:"👩‍🎤",woman_student:"👩‍🎓",woman_teacher:"👩‍🏫",woman_technologist:"👩‍💻",woman_with_turban:"👳‍♀️",womans_clothes:"👚",womans_hat:"👒",women_wrestling:"🤼‍♀️",womens:"🚺",world_map:"🗺",worried:"😟",wrench:"🔧",writing_hand:"✍️",x:"❌",yellow_heart:"💛",yen:"💴",yin_yang:"☯️",yum:"😋",zap:"⚡️",zipper_mouth_face:"🤐",zzz:"💤",octocat:':octocat:',showdown:"S"},x.Converter=function(e){"use strict";var r,t,n={},i=[],l=[],o={},a=h,s={parsed:{},raw:"",format:""};for(r in e=e||{},p)p.hasOwnProperty(r)&&(n[r]=p[r]);if("object"!=typeof e)throw Error("Converter expects the passed parameter to be an object, but "+typeof e+" was passed instead.");for(t in e)e.hasOwnProperty(t)&&(n[t]=e[t]);function c(e,r){if(r=r||null,x.helper.isString(e)){if(r=e=x.helper.stdExtName(e),x.extensions[e]){console.warn("DEPRECATION WARNING: "+e+" is an old extension that uses a deprecated loading method.Please inform the developer that the extension should be updated!");var t=x.extensions[e],a=e;if("function"==typeof t&&(t=t(new x.Converter)),x.helper.isArray(t)||(t=[t]),!(a=g(t,a)).valid)throw Error(a.error);for(var n=0;n[ \t]+¨NBSP;<"),!r){if(!window||!window.document)throw new Error("HTMLParser is undefined. If in a webworker or nodejs environment, you need to provide a WHATWG DOM and HTML such as JSDOM");r=window.document}for(var r=r.createElement("div"),t=(r.innerHTML=e,{preList:function(e){for(var r=e.querySelectorAll("pre"),t=[],a=0;a'}else t.push(r[a].innerHTML),r[a].innerHTML="",r[a].setAttribute("prenum",a.toString());return t}(r)}),a=(!function e(r){for(var t=0;t? ?(['"].*['"])?\)$/m))a="";else if(!a){if(a="#"+(t=t||r.toLowerCase().replace(/ ?\n/g," ")),x.helper.isUndefined(l.gUrls[t]))return e;a=l.gUrls[t],x.helper.isUndefined(l.gTitles[t])||(o=l.gTitles[t])}return e='"}return e=(e=(e=(e=(e=l.converter._dispatch("anchors.before",e,i,l)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)] ?(?:\n *)?\[(.*?)]()()()()/g,r)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)]()[ \t]*\([ \t]?<([^>]*)>(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g,r)).replace(/\[((?:\[[^\]]*]|[^\[\]])*)]()[ \t]*\([ \t]??(?:[ \t]*((["'])([^"]*?)\5))?[ \t]?\)/g,r)).replace(/\[([^\[\]]+)]()()()()()/g,r),i.ghMentions&&(e=e.replace(/(^|\s)(\\)?(@([a-z\d]+(?:[a-z\d.-]+?[a-z\d]+)*))/gim,function(e,r,t,a,n){if("\\"===t)return r+a;if(!x.helper.isString(i.ghMentionsLink))throw new Error("ghMentionsLink option must be a string");t="";return r+'"+a+""})),e=l.converter._dispatch("anchors.after",e,i,l)});var i=/([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+?\.[^'">\s]+?)()(\1)?(?=\s|$)(?!["<>])/gi,l=/([*~_]+|\b)(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+?)([.!?,()\[\]])?(\1)?(?=\s|$)(?!["<>])/gi,c=/()<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)()>()/gi,m=/(^|\s)(?:mailto:)?([A-Za-z0-9!#$%&'*+-/=?^_`{|}~.]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)(?=$|\s)/gim,f=/<()(?:mailto:)?([-.\w]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi;x.subParser("autoLinks",function(e,r,t){"use strict";return e=(e=(e=t.converter._dispatch("autoLinks.before",e,r,t)).replace(c,s(r))).replace(f,o(r,t)),e=t.converter._dispatch("autoLinks.after",e,r,t)}),x.subParser("simplifiedAutoLinks",function(e,r,t){"use strict";return r.simplifiedAutoLink?(e=t.converter._dispatch("simplifiedAutoLinks.before",e,r,t),e=(e=r.excludeTrailingPunctuationFromURLs?e.replace(l,s(r)):e.replace(i,s(r))).replace(m,o(r,t)),t.converter._dispatch("simplifiedAutoLinks.after",e,r,t)):e}),x.subParser("blockGamut",function(e,r,t){"use strict";return e=t.converter._dispatch("blockGamut.before",e,r,t),e=x.subParser("blockQuotes")(e,r,t),e=x.subParser("headers")(e,r,t),e=x.subParser("horizontalRule")(e,r,t),e=x.subParser("lists")(e,r,t),e=x.subParser("codeBlocks")(e,r,t),e=x.subParser("tables")(e,r,t),e=x.subParser("hashHTMLBlocks")(e,r,t),e=x.subParser("paragraphs")(e,r,t),e=t.converter._dispatch("blockGamut.after",e,r,t)}),x.subParser("blockQuotes",function(e,r,t){"use strict";e=t.converter._dispatch("blockQuotes.before",e,r,t);var a=/(^ {0,3}>[ \t]?.+\n(.+\n)*\n*)+/gm;return r.splitAdjacentBlockquotes&&(a=/^ {0,3}>[\s\S]*?(?:\n\n)/gm),e=(e+="\n\n").replace(a,function(e){return e=(e=(e=e.replace(/^[ \t]*>[ \t]?/gm,"")).replace(/¨0/g,"")).replace(/^[ \t]+$/gm,""),e=x.subParser("githubCodeBlocks")(e,r,t),e=(e=(e=x.subParser("blockGamut")(e,r,t)).replace(/(^|\n)/g,"$1 ")).replace(/(\s*
[^\r]+?<\/pre>)/gm,function(e,r){return r.replace(/^  /gm,"¨0").replace(/¨0/g,"")}),x.subParser("hashBlock")("
\n"+e+"\n
",r,t)}),e=t.converter._dispatch("blockQuotes.after",e,r,t)}),x.subParser("codeBlocks",function(e,n,s){"use strict";e=s.converter._dispatch("codeBlocks.before",e,n,s);return e=(e=(e+="¨0").replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=¨0))/g,function(e,r,t){var a="\n",r=x.subParser("outdent")(r,n,s);return r=x.subParser("encodeCode")(r,n,s),r="
"+(r=(r=(r=x.subParser("detab")(r,n,s)).replace(/^\n+/g,"")).replace(/\n+$/g,""))+(a=n.omitExtraWLInCodeBlocks?"":a)+"
",x.subParser("hashBlock")(r,n,s)+t})).replace(/¨0/,""),e=s.converter._dispatch("codeBlocks.after",e,n,s)}),x.subParser("codeSpans",function(e,n,s){"use strict";return e=(e=void 0===(e=s.converter._dispatch("codeSpans.before",e,n,s))?"":e).replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(e,r,t,a){return a=(a=a.replace(/^([ \t]*)/g,"")).replace(/[ \t]*$/g,""),a=r+""+(a=x.subParser("encodeCode")(a,n,s))+"",a=x.subParser("hashHTMLSpans")(a,n,s)}),e=s.converter._dispatch("codeSpans.after",e,n,s)}),x.subParser("completeHTMLDocument",function(e,r,t){"use strict";if(!r.completeHTMLDocument)return e;e=t.converter._dispatch("completeHTMLDocument.before",e,r,t);var a,n="html",s="\n",o="",i='\n',l="",c="";for(a in void 0!==t.metadata.parsed.doctype&&(s="\n","html"!==(n=t.metadata.parsed.doctype.toString().toLowerCase())&&"html5"!==n||(i='')),t.metadata.parsed)if(t.metadata.parsed.hasOwnProperty(a))switch(a.toLowerCase()){case"doctype":break;case"title":o=""+t.metadata.parsed.title+"\n";break;case"charset":i="html"===n||"html5"===n?'\n':'\n';break;case"language":case"lang":l=' lang="'+t.metadata.parsed[a]+'"',c+='\n';break;default:c+='\n'}return e=s+"\n\n"+o+i+c+"\n\n"+e.trim()+"\n\n",e=t.converter._dispatch("completeHTMLDocument.after",e,r,t)}),x.subParser("detab",function(e,r,t){"use strict";return e=(e=(e=(e=(e=(e=t.converter._dispatch("detab.before",e,r,t)).replace(/\t(?=\t)/g," ")).replace(/\t/g,"¨A¨B")).replace(/¨B(.+?)¨A/g,function(e,r){for(var t=r,a=4-t.length%4,n=0;n/g,">"),e=t.converter._dispatch("encodeAmpsAndAngles.after",e,r,t)}),x.subParser("encodeBackslashEscapes",function(e,r,t){"use strict";return e=(e=(e=t.converter._dispatch("encodeBackslashEscapes.before",e,r,t)).replace(/\\(\\)/g,x.helper.escapeCharactersCallback)).replace(/\\([`*_{}\[\]()>#+.!~=|:-])/g,x.helper.escapeCharactersCallback),e=t.converter._dispatch("encodeBackslashEscapes.after",e,r,t)}),x.subParser("encodeCode",function(e,r,t){"use strict";return e=(e=t.converter._dispatch("encodeCode.before",e,r,t)).replace(/&/g,"&").replace(//g,">").replace(/([*_{}\[\]\\=~-])/g,x.helper.escapeCharactersCallback),e=t.converter._dispatch("encodeCode.after",e,r,t)}),x.subParser("escapeSpecialCharsWithinTagAttributes",function(e,r,t){"use strict";return e=(e=(e=t.converter._dispatch("escapeSpecialCharsWithinTagAttributes.before",e,r,t)).replace(/<\/?[a-z\d_:-]+(?:[\s]+[\s\S]+?)?>/gi,function(e){return e.replace(/(.)<\/?code>(?=.)/g,"$1`").replace(/([\\`*_~=|])/g,x.helper.escapeCharactersCallback)})).replace(/-]|-[^>])(?:[^-]|-[^-])*)--)>/gi,function(e){return e.replace(/([\\`*_~=|])/g,x.helper.escapeCharactersCallback)}),e=t.converter._dispatch("escapeSpecialCharsWithinTagAttributes.after",e,r,t)}),x.subParser("githubCodeBlocks",function(e,s,o){"use strict";return s.ghCodeBlocks?(e=o.converter._dispatch("githubCodeBlocks.before",e,s,o),e=(e=(e+="¨0").replace(/(?:^|\n)(?: {0,3})(```+|~~~+)(?: *)([^\s`~]*)\n([\s\S]*?)\n(?: {0,3})\1/g,function(e,r,t,a){var n=s.omitExtraWLInCodeBlocks?"":"\n";return a=x.subParser("encodeCode")(a,s,o),a="
"+(a=(a=(a=x.subParser("detab")(a,s,o)).replace(/^\n+/g,"")).replace(/\n+$/g,""))+n+"
",a=x.subParser("hashBlock")(a,s,o),"\n\n¨G"+(o.ghCodeBlocks.push({text:e,codeblock:a})-1)+"G\n\n"})).replace(/¨0/,""),o.converter._dispatch("githubCodeBlocks.after",e,s,o)):e}),x.subParser("hashBlock",function(e,r,t){"use strict";return e=(e=t.converter._dispatch("hashBlock.before",e,r,t)).replace(/(^\n+|\n+$)/g,""),e="\n\n¨K"+(t.gHtmlBlocks.push(e)-1)+"K\n\n",e=t.converter._dispatch("hashBlock.after",e,r,t)}),x.subParser("hashCodeTags",function(e,n,s){"use strict";e=s.converter._dispatch("hashCodeTags.before",e,n,s);return e=x.helper.replaceRecursiveRegExp(e,function(e,r,t,a){t=t+x.subParser("encodeCode")(r,n,s)+a;return"¨C"+(s.gHtmlSpans.push(t)-1)+"C"},"]*>","","gim"),e=s.converter._dispatch("hashCodeTags.after",e,n,s)}),x.subParser("hashElement",function(e,r,t){"use strict";return function(e,r){return r=(r=(r=r.replace(/\n\n/g,"\n")).replace(/^\n/,"")).replace(/\n+$/g,""),r="\n\n¨K"+(t.gHtmlBlocks.push(r)-1)+"K\n\n"}}),x.subParser("hashHTMLBlocks",function(e,r,n){"use strict";e=n.converter._dispatch("hashHTMLBlocks.before",e,r,n);function t(e,r,t,a){return-1!==t.search(/\bmarkdown\b/)&&(e=t+n.converter.makeHtml(r)+a),"\n\n¨K"+(n.gHtmlBlocks.push(e)-1)+"K\n\n"}var a=["pre","div","h1","h2","h3","h4","h5","h6","blockquote","table","dl","ol","ul","script","noscript","form","fieldset","iframe","math","style","section","header","footer","nav","article","aside","address","audio","canvas","figure","hgroup","output","video","p"];r.backslashEscapesHTMLTags&&(e=e.replace(/\\<(\/?[^>]+?)>/g,function(e,r){return"<"+r+">"}));for(var s=0;s]*>)","im"),i="<"+a[s]+"\\b[^>]*>",l="";-1!==(c=x.helper.regexIndexOf(e,o));){var c=x.helper.splitAtIndex(e,c),u=x.helper.replaceRecursiveRegExp(c[1],t,i,l,"im");if(u===c[1])break;e=c[0].concat(u)}return e=e.replace(/(\n {0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,x.subParser("hashElement")(e,r,n)),e=(e=x.helper.replaceRecursiveRegExp(e,function(e){return"\n\n¨K"+(n.gHtmlBlocks.push(e)-1)+"K\n\n"},"^ {0,3}\x3c!--","--\x3e","gm")).replace(/(?:\n\n)( {0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,x.subParser("hashElement")(e,r,n)),e=n.converter._dispatch("hashHTMLBlocks.after",e,r,n)}),x.subParser("hashHTMLSpans",function(e,r,t){"use strict";function a(e){return"¨C"+(t.gHtmlSpans.push(e)-1)+"C"}return e=(e=(e=(e=(e=t.converter._dispatch("hashHTMLSpans.before",e,r,t)).replace(/<[^>]+?\/>/gi,a)).replace(/<([^>]+?)>[\s\S]*?<\/\1>/g,a)).replace(/<([^>]+?)\s[^>]+?>[\s\S]*?<\/\1>/g,a)).replace(/<[^>]+?>/gi,a),e=t.converter._dispatch("hashHTMLSpans.after",e,r,t)}),x.subParser("unhashHTMLSpans",function(e,r,t){"use strict";e=t.converter._dispatch("unhashHTMLSpans.before",e,r,t);for(var a=0;a]*>\\s*]*>","^ {0,3}\\s*
","gim"),e=s.converter._dispatch("hashPreCodeTags.after",e,n,s)}),x.subParser("headers",function(e,n,s){"use strict";e=s.converter._dispatch("headers.before",e,n,s);var o=isNaN(parseInt(n.headerLevelStart))?1:parseInt(n.headerLevelStart),r=n.smoothLivePreview?/^(.+)[ \t]*\n={2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n=+[ \t]*\n+/gm,t=n.smoothLivePreview?/^(.+)[ \t]*\n-{2,}[ \t]*\n+/gm:/^(.+)[ \t]*\n-+[ \t]*\n+/gm,r=(e=(e=e.replace(r,function(e,r){var t=x.subParser("spanGamut")(r,n,s),r=n.noHeaderId?"":' id="'+i(r)+'"',r=""+t+"";return x.subParser("hashBlock")(r,n,s)})).replace(t,function(e,r){var t=x.subParser("spanGamut")(r,n,s),r=n.noHeaderId?"":' id="'+i(r)+'"',a=o+1,r=""+t+"";return x.subParser("hashBlock")(r,n,s)}),n.requireSpaceBeforeHeadingText?/^(#{1,6})[ \t]+(.+?)[ \t]*#*\n+/gm:/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm);function i(e){var r=e=n.customizedHeaderId&&(r=e.match(/\{([^{]+?)}\s*$/))&&r[1]?r[1]:e,e=x.helper.isString(n.prefixHeaderId)?n.prefixHeaderId:!0===n.prefixHeaderId?"section-":"";return n.rawPrefixHeaderId||(r=e+r),r=(n.ghCompatibleHeaderId?r.replace(/ /g,"-").replace(/&/g,"").replace(/¨T/g,"").replace(/¨D/g,"").replace(/[&+$,\/:;=?@"#{}|^¨~\[\]`\\*)(%.!'<>]/g,""):n.rawHeaderId?r.replace(/ /g,"-").replace(/&/g,"&").replace(/¨T/g,"¨").replace(/¨D/g,"$").replace(/["']/g,"-"):r.replace(/[^\w]/g,"")).toLowerCase(),n.rawPrefixHeaderId&&(r=e+r),s.hashLinkCounts[r]?r=r+"-"+s.hashLinkCounts[r]++:s.hashLinkCounts[r]=1,r}return e=e.replace(r,function(e,r,t){var a=t,a=(n.customizedHeaderId&&(a=t.replace(/\s?\{([^{]+?)}\s*$/,"")),x.subParser("spanGamut")(a,n,s)),t=n.noHeaderId?"":' id="'+i(t)+'"',r=o-1+r.length,t=""+a+"";return x.subParser("hashBlock")(t,n,s)}),e=s.converter._dispatch("headers.after",e,n,s)}),x.subParser("horizontalRule",function(e,r,t){"use strict";e=t.converter._dispatch("horizontalRule.before",e,r,t);var a=x.subParser("hashBlock")("
",r,t);return e=(e=(e=e.replace(/^ {0,2}( ?-){3,}[ \t]*$/gm,a)).replace(/^ {0,2}( ?\*){3,}[ \t]*$/gm,a)).replace(/^ {0,2}( ?_){3,}[ \t]*$/gm,a),e=t.converter._dispatch("horizontalRule.after",e,r,t)}),x.subParser("images",function(e,r,d){"use strict";function l(e,r,t,a,n,s,o,i){var l=d.gUrls,c=d.gTitles,u=d.gDimensions;if(t=t.toLowerCase(),i=i||"",-1? ?(['"].*['"])?\)$/m))a="";else if(""===a||null===a){if(a="#"+(t=""!==t&&null!==t?t:r.toLowerCase().replace(/ ?\n/g," ")),x.helper.isUndefined(l[t]))return e;a=l[t],x.helper.isUndefined(c[t])||(i=c[t]),x.helper.isUndefined(u[t])||(n=u[t].width,s=u[t].height)}r=r.replace(/"/g,""").replace(x.helper.regexes.asteriskDashAndColon,x.helper.escapeCharactersCallback);e=''+r+'"}return e=(e=(e=(e=(e=(e=d.converter._dispatch("images.before",e,r,d)).replace(/!\[([^\]]*?)] ?(?:\n *)?\[([\s\S]*?)]()()()()()/g,l)).replace(/!\[([^\]]*?)][ \t]*()\([ \t]??(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,function(e,r,t,a,n,s,o,i){return l(e,r,t,a=a.replace(/\s/g,""),n,s,0,i)})).replace(/!\[([^\]]*?)][ \t]*()\([ \t]?<([^>]*)>(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(?:(["'])([^"]*?)\6))?[ \t]?\)/g,l)).replace(/!\[([^\]]*?)][ \t]*()\([ \t]??(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(["'])([^"]*?)\6)?[ \t]?\)/g,l)).replace(/!\[([^\[\]]+)]()()()()()/g,l),e=d.converter._dispatch("images.after",e,r,d)}),x.subParser("italicsAndBold",function(e,r,t){"use strict";return e=t.converter._dispatch("italicsAndBold.before",e,r,t),e=r.literalMidWordUnderscores?(e=(e=e.replace(/\b___(\S[\s\S]*?)___\b/g,function(e,r){return""+r+""})).replace(/\b__(\S[\s\S]*?)__\b/g,function(e,r){return""+r+""})).replace(/\b_(\S[\s\S]*?)_\b/g,function(e,r){return""+r+""}):(e=(e=e.replace(/___(\S[\s\S]*?)___/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/__(\S[\s\S]*?)__/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/_([^\s_][\s\S]*?)_/g,function(e,r){return/\S$/.test(r)?""+r+"":e}),e=r.literalMidWordAsterisks?(e=(e=e.replace(/([^*]|^)\B\*\*\*(\S[\s\S]*?)\*\*\*\B(?!\*)/g,function(e,r,t){return r+""+t+""})).replace(/([^*]|^)\B\*\*(\S[\s\S]*?)\*\*\B(?!\*)/g,function(e,r,t){return r+""+t+""})).replace(/([^*]|^)\B\*(\S[\s\S]*?)\*\B(?!\*)/g,function(e,r,t){return r+""+t+""}):(e=(e=e.replace(/\*\*\*(\S[\s\S]*?)\*\*\*/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/\*\*(\S[\s\S]*?)\*\*/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/\*([^\s*][\s\S]*?)\*/g,function(e,r){return/\S$/.test(r)?""+r+"":e}),e=t.converter._dispatch("italicsAndBold.after",e,r,t)}),x.subParser("lists",function(e,d,c){"use strict";function p(e,r){c.gListLevel++,e=e.replace(/\n{2,}$/,"\n");var t=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0| {0,3}([*+-]|\d+[.])[ \t]+))/gm,l=/\n[ \t]*\n(?!¨0)/.test(e+="¨0");return d.disableForced4SpacesIndentedSublists&&(t=/(\n)?(^ {0,3})([*+-]|\d+[.])[ \t]+((\[(x|X| )?])?[ \t]*[^\r]+?(\n{1,2}))(?=\n*(¨0|\2([*+-]|\d+[.])[ \t]+))/gm),e=(e=e.replace(t,function(e,r,t,a,n,s,o){o=o&&""!==o.trim();var n=x.subParser("outdent")(n,d,c),i="";return s&&d.tasklists&&(i=' class="task-list-item" style="list-style-type: none;"',n=n.replace(/^[ \t]*\[(x|X| )?]/m,function(){var e='"+(n=(n=r||-1\n"})).replace(/¨0/g,""),c.gListLevel--,e=r?e.replace(/\s+$/,""):e}function h(e,r){if("ol"===r){r=e.match(/^ *(\d+)\./);if(r&&"1"!==r[1])return' start="'+r[1]+'"'}return""}function n(n,s,o){var e,i=d.disableForced4SpacesIndentedSublists?/^ ?\d+\.[ \t]/gm:/^ {0,3}\d+\.[ \t]/gm,l=d.disableForced4SpacesIndentedSublists?/^ ?[*+-][ \t]/gm:/^ {0,3}[*+-][ \t]/gm,c="ul"===s?i:l,u="";return-1!==n.search(c)?function e(r){var t=r.search(c),a=h(n,s);-1!==t?(u+="\n\n<"+s+a+">\n"+p(r.slice(0,t),!!o)+"\n",c="ul"===(s="ul"===s?"ol":"ul")?i:l,e(r.slice(t))):u+="\n\n<"+s+a+">\n"+p(r,!!o)+"\n"}(n):(e=h(n,s),u="\n\n<"+s+e+">\n"+p(n,!!o)+"\n"),u}return e=c.converter._dispatch("lists.before",e,d,c),e+="¨0",e=(e=c.gListLevel?e.replace(/^(( {0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(¨0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm,function(e,r,t){return n(r,-1"),i+="

",n.push(i))}for(s=n.length,o=0;o]*>\s*]*>/.test(c)&&(u=!0)}n[o]=c}return e=(e=(e=n.join("\n")).replace(/^\n+/g,"")).replace(/\n+$/g,""),t.converter._dispatch("paragraphs.after",e,r,t)}),x.subParser("runExtension",function(e,r,t,a){"use strict";return e.filter?r=e.filter(r,a.converter,t):e.regex&&((a=e.regex)instanceof RegExp||(a=new RegExp(a,"g")),r=r.replace(a,e.replace)),r}),x.subParser("spanGamut",function(e,r,t){"use strict";return e=t.converter._dispatch("spanGamut.before",e,r,t),e=x.subParser("codeSpans")(e,r,t),e=x.subParser("escapeSpecialCharsWithinTagAttributes")(e,r,t),e=x.subParser("encodeBackslashEscapes")(e,r,t),e=x.subParser("images")(e,r,t),e=x.subParser("anchors")(e,r,t),e=x.subParser("autoLinks")(e,r,t),e=x.subParser("simplifiedAutoLinks")(e,r,t),e=x.subParser("emoji")(e,r,t),e=x.subParser("underline")(e,r,t),e=x.subParser("italicsAndBold")(e,r,t),e=x.subParser("strikethrough")(e,r,t),e=x.subParser("ellipsis")(e,r,t),e=x.subParser("hashHTMLSpans")(e,r,t),e=x.subParser("encodeAmpsAndAngles")(e,r,t),r.simpleLineBreaks?/\n\n¨K/.test(e)||(e=e.replace(/\n+/g,"
\n")):e=e.replace(/ +\n/g,"
\n"),e=t.converter._dispatch("spanGamut.after",e,r,t)}),x.subParser("strikethrough",function(e,t,a){"use strict";return t.strikethrough&&(e=(e=a.converter._dispatch("strikethrough.before",e,t,a)).replace(/(?:~){2}([\s\S]+?)(?:~){2}/g,function(e,r){return r=r,""+(r=t.simplifiedAutoLink?x.subParser("simplifiedAutoLinks")(r,t,a):r)+""}),e=a.converter._dispatch("strikethrough.after",e,t,a)),e}),x.subParser("stripLinkDefinitions",function(i,l,c){"use strict";function e(e,r,t,a,n,s,o){return r=r.toLowerCase(),i.toLowerCase().split(r).length-1<2?e:(t.match(/^data:.+?\/.+?;base64,/)?c.gUrls[r]=t.replace(/\s/g,""):c.gUrls[r]=x.subParser("encodeAmpsAndAngles")(t,l,c),s?s+o:(o&&(c.gTitles[r]=o.replace(/"|'/g,""")),l.parseImgDimensions&&a&&n&&(c.gDimensions[r]={width:a,height:n}),""))}return i=(i=(i=(i+="¨0").replace(/^ {0,3}\[([^\]]+)]:[ \t]*\n?[ \t]*?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n\n|(?=¨0)|(?=\n\[))/gm,e)).replace(/^ {0,3}\[([^\]]+)]:[ \t]*\n?[ \t]*\s]+)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*\n?[ \t]*(?:(\n*)["|'(](.+?)["|')][ \t]*)?(?:\n+|(?=¨0))/gm,e)).replace(/¨0/,"")}),x.subParser("tables",function(e,y,P){"use strict";if(!y.tables)return e;function r(e){for(var r=e.split("\n"),t=0;t"+(n=x.subParser("spanGamut")(n,y,P))+"\n"));for(t=0;t"+x.subParser("spanGamut")(i,y,P)+"\n"));h.push(_)}for(var m=d,f=h,b="\n\n\n",w=m.length,k=0;k\n\n\n",k=0;k\n";for(var v=0;v\n"}return b+="\n
\n"}return e=(e=(e=(e=P.converter._dispatch("tables.before",e,y,P)).replace(/\\(\|)/g,x.helper.escapeCharactersCallback)).replace(/^ {0,3}\|?.+\|.+\n {0,3}\|?[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*:?[ \t]*(?:[-=]){2,}[\s\S]+?(?:\n\n|¨0)/gm,r)).replace(/^ {0,3}\|.+\|[ \t]*\n {0,3}\|[ \t]*:?[ \t]*(?:[-=]){2,}[ \t]*:?[ \t]*\|[ \t]*\n( {0,3}\|.+\|[ \t]*\n)*(?:\n|¨0)/gm,r),e=P.converter._dispatch("tables.after",e,y,P)}),x.subParser("underline",function(e,r,t){"use strict";return r.underline?(e=t.converter._dispatch("underline.before",e,r,t),e=(e=r.literalMidWordUnderscores?(e=e.replace(/\b___(\S[\s\S]*?)___\b/g,function(e,r){return""+r+""})).replace(/\b__(\S[\s\S]*?)__\b/g,function(e,r){return""+r+""}):(e=e.replace(/___(\S[\s\S]*?)___/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/__(\S[\s\S]*?)__/g,function(e,r){return/\S$/.test(r)?""+r+"":e})).replace(/(_)/g,x.helper.escapeCharactersCallback),t.converter._dispatch("underline.after",e,r,t)):e}),x.subParser("unescapeSpecialChars",function(e,r,t){"use strict";return e=(e=t.converter._dispatch("unescapeSpecialChars.before",e,r,t)).replace(/¨E(\d+)E/g,function(e,r){r=parseInt(r);return String.fromCharCode(r)}),e=t.converter._dispatch("unescapeSpecialChars.after",e,r,t)}),x.subParser("makeMarkdown.blockquote",function(e,r){"use strict";var t="";if(e.hasChildNodes())for(var a=e.childNodes,n=a.length,s=0;s ")}),x.subParser("makeMarkdown.codeBlock",function(e,r){"use strict";var t=e.getAttribute("language"),e=e.getAttribute("precodenum");return"```"+t+"\n"+r.preList[e]+"\n```"}),x.subParser("makeMarkdown.codeSpan",function(e){"use strict";return"`"+e.innerHTML+"`"}),x.subParser("makeMarkdown.emphasis",function(e,r){"use strict";var t="";if(e.hasChildNodes()){t+="*";for(var a=e.childNodes,n=a.length,s=0;s",e.hasAttribute("width")&&e.hasAttribute("height")&&(r+=" ="+e.getAttribute("width")+"x"+e.getAttribute("height")),e.hasAttribute("title")&&(r+=' "'+e.getAttribute("title")+'"'),r+=")"),r}),x.subParser("makeMarkdown.links",function(e,r){"use strict";var t="";if(e.hasChildNodes()&&e.hasAttribute("href")){for(var a=e.childNodes,n=a.length,t="[",s=0;s"),e.hasAttribute("title")&&(t+=' "'+e.getAttribute("title")+'"'),t+=")"}return t}),x.subParser("makeMarkdown.list",function(e,r,t){"use strict";var a="";if(!e.hasChildNodes())return"";for(var n=e.childNodes,s=n.length,o=e.getAttribute("start")||1,i=0;i"+r.preList[e]+""}),x.subParser("makeMarkdown.strikethrough",function(e,r){"use strict";var t="";if(e.hasChildNodes()){t+="~~";for(var a=e.childNodes,n=a.length,s=0;str>th"),s=e.querySelectorAll("tbody>tr"),o=0;o/g,"\\$1>")).replace(/^#/gm,"\\#")).replace(/^(\s*)([-=]{3,})(\s*)$/,"$1\\$2$3")).replace(/^( {0,3}\d+)\./gm,"$1\\.")).replace(/^( {0,3})([+-])/gm,"$1\\$2")).replace(/]([\s]*)\(/g,"\\]$1\\(")).replace(/^ {0,3}\[([\S \t]*?)]:/gm,"\\[$1]:")});"function"==typeof define&&define.amd?define(function(){"use strict";return x}):"undefined"!=typeof module&&module.exports?module.exports=x:this.showdown=x}.call(this); -//# sourceMappingURL=showdown.min.js.map +//# sourceMappingURL=showdown.min.js.map diff --git a/public/scripts/showdown.min.js.map b/public/lib/showdown.min.js.map similarity index 100% rename from public/scripts/showdown.min.js.map rename to public/lib/showdown.min.js.map diff --git a/public/lib/svg-inject.js b/public/lib/svg-inject.js new file mode 100644 index 000000000..4e74af07c --- /dev/null +++ b/public/lib/svg-inject.js @@ -0,0 +1,697 @@ +/** + * SVGInject - Version 1.2.3 + * A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM. + * + * https://github.com/iconfu/svg-inject + * + * Copyright (c) 2018 INCORS, the creators of iconfu.com + * @license MIT License - https://github.com/iconfu/svg-inject/blob/master/LICENSE + */ + +(function(window, document) { + // constants for better minification + var _CREATE_ELEMENT_ = 'createElement'; + var _GET_ELEMENTS_BY_TAG_NAME_ = 'getElementsByTagName'; + var _LENGTH_ = 'length'; + var _STYLE_ = 'style'; + var _TITLE_ = 'title'; + var _UNDEFINED_ = 'undefined'; + var _SET_ATTRIBUTE_ = 'setAttribute'; + var _GET_ATTRIBUTE_ = 'getAttribute'; + + var NULL = null; + + // constants + var __SVGINJECT = '__svgInject'; + var ID_SUFFIX = '--inject-'; + var ID_SUFFIX_REGEX = new RegExp(ID_SUFFIX + '\\d+', "g"); + var LOAD_FAIL = 'LOAD_FAIL'; + var SVG_NOT_SUPPORTED = 'SVG_NOT_SUPPORTED'; + var SVG_INVALID = 'SVG_INVALID'; + var ATTRIBUTE_EXCLUSION_NAMES = ['src', 'alt', 'onload', 'onerror']; + var A_ELEMENT = document[_CREATE_ELEMENT_]('a'); + var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_; + var DEFAULT_OPTIONS = { + useCache: true, + copyAttributes: true, + makeIdsUnique: true + }; + // Map of IRI referenceable tag names to properties that can reference them. This is defined in + // https://www.w3.org/TR/SVG11/linking.html#processingIRI + var IRI_TAG_PROPERTIES_MAP = { + clipPath: ['clip-path'], + 'color-profile': NULL, + cursor: NULL, + filter: NULL, + linearGradient: ['fill', 'stroke'], + marker: ['marker', 'marker-end', 'marker-mid', 'marker-start'], + mask: NULL, + pattern: ['fill', 'stroke'], + radialGradient: ['fill', 'stroke'] + }; + var INJECTED = 1; + var FAIL = 2; + + var uniqueIdCounter = 1; + var xmlSerializer; + var domParser; + + + // creates an SVG document from an SVG string + function svgStringToSvgDoc(svgStr) { + domParser = domParser || new DOMParser(); + return domParser.parseFromString(svgStr, 'text/xml'); + } + + + // searializes an SVG element to an SVG string + function svgElemToSvgString(svgElement) { + xmlSerializer = xmlSerializer || new XMLSerializer(); + return xmlSerializer.serializeToString(svgElement); + } + + + // Returns the absolute url for the specified url + function getAbsoluteUrl(url) { + A_ELEMENT.href = url; + return A_ELEMENT.href; + } + + + // Load svg with an XHR request + function loadSvg(url, callback, errorCallback) { + if (url) { + var req = new XMLHttpRequest(); + req.onreadystatechange = function() { + if (req.readyState == 4) { + // readyState is DONE + var status = req.status; + if (status == 200) { + // request status is OK + callback(req.responseXML, req.responseText.trim()); + } else if (status >= 400) { + // request status is error (4xx or 5xx) + errorCallback(); + } else if (status == 0) { + // request status 0 can indicate a failed cross-domain call + errorCallback(); + } + } + }; + req.open('GET', url, true); + req.send(); + } + } + + + // Copy attributes from img element to svg element + function copyAttributes(imgElem, svgElem) { + var attribute; + var attributeName; + var attributeValue; + var attributes = imgElem.attributes; + for (var i = 0; i < attributes[_LENGTH_]; i++) { + attribute = attributes[i]; + attributeName = attribute.name; + // Only copy attributes not explicitly excluded from copying + if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) { + attributeValue = attribute.value; + // If img attribute is "title", insert a title element into SVG element + if (attributeName == _TITLE_) { + var titleElem; + var firstElementChild = svgElem.firstElementChild; + if (firstElementChild && firstElementChild.localName.toLowerCase() == _TITLE_) { + // If the SVG element's first child is a title element, keep it as the title element + titleElem = firstElementChild; + } else { + // If the SVG element's first child element is not a title element, create a new title + // ele,emt and set it as the first child + titleElem = document[_CREATE_ELEMENT_ + 'NS']('http://www.w3.org/2000/svg', _TITLE_); + svgElem.insertBefore(titleElem, firstElementChild); + } + // Set new title content + titleElem.textContent = attributeValue; + } else { + // Set img attribute to svg element + svgElem[_SET_ATTRIBUTE_](attributeName, attributeValue); + } + } + } + } + + + // This function appends a suffix to IDs of referenced elements in the in order to to avoid ID collision + // between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is + // incremented with each injection. References to the IDs are adjusted accordingly. + // We assume tha all IDs within the injected SVG are unique, therefore the same suffix can be used for all IDs of one + // injected SVG. + // If the onlyReferenced argument is set to true, only those IDs will be made unique that are referenced from within the SVG + function makeIdsUnique(svgElem, onlyReferenced) { + var idSuffix = ID_SUFFIX + uniqueIdCounter++; + // Regular expression for functional notations of an IRI references. This will find occurences in the form + // url(#anyId) or url("#anyId") (for Internet Explorer) and capture the referenced ID + var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g; + // Get all elements with an ID. The SVG spec recommends to put referenced elements inside elements, but + // this is not a requirement, therefore we have to search for IDs in the whole SVG. + var idElements = svgElem.querySelectorAll('[id]'); + var idElem; + // An object containing referenced IDs as keys is used if only referenced IDs should be uniquified. + // If this object does not exist, all IDs will be uniquified. + var referencedIds = onlyReferenced ? [] : NULL; + var tagName; + var iriTagNames = {}; + var iriProperties = []; + var changed = false; + var i, j; + + if (idElements[_LENGTH_]) { + // Make all IDs unique by adding the ID suffix and collect all encountered tag names + // that are IRI referenceable from properities. + for (i = 0; i < idElements[_LENGTH_]; i++) { + tagName = idElements[i].localName; // Use non-namespaced tag name + // Make ID unique if tag name is IRI referenceable + if (tagName in IRI_TAG_PROPERTIES_MAP) { + iriTagNames[tagName] = 1; + } + } + // Get all properties that are mapped to the found IRI referenceable tags + for (tagName in iriTagNames) { + (IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) { + // Add mapped properties to array of iri referencing properties. + // Use linear search here because the number of possible entries is very small (maximum 11) + if (iriProperties.indexOf(mappedProperty) < 0) { + iriProperties.push(mappedProperty); + } + }); + } + if (iriProperties[_LENGTH_]) { + // Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"' + iriProperties.push(_STYLE_); + } + // Run through all elements of the SVG and replace IDs in references. + // To get all descending elements, getElementsByTagName('*') seems to perform faster than querySelectorAll('*'). + // Since svgElem.getElementsByTagName('*') does not return the svg element itself, we have to handle it separately. + var descElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*'); + var element = svgElem; + var propertyName; + var value; + var newValue; + for (i = -1; element != NULL;) { + if (element.localName == _STYLE_) { + // If element is a style element, replace IDs in all occurences of "url(#anyId)" in text content + value = element.textContent; + newValue = value && value.replace(funcIriRegex, function(match, id) { + if (referencedIds) { + referencedIds[id] = 1; + } + return 'url(#' + id + idSuffix + ')'; + }); + if (newValue !== value) { + element.textContent = newValue; + } + } else if (element.hasAttributes()) { + // Run through all property names for which IDs were found + for (j = 0; j < iriProperties[_LENGTH_]; j++) { + propertyName = iriProperties[j]; + value = element[_GET_ATTRIBUTE_](propertyName); + newValue = value && value.replace(funcIriRegex, function(match, id) { + if (referencedIds) { + referencedIds[id] = 1; + } + return 'url(#' + id + idSuffix + ')'; + }); + if (newValue !== value) { + element[_SET_ATTRIBUTE_](propertyName, newValue); + } + } + // Replace IDs in xlink:ref and href attributes + ['xlink:href', 'href'].forEach(function(refAttrName) { + var iri = element[_GET_ATTRIBUTE_](refAttrName); + if (/^\s*#/.test(iri)) { // Check if iri is non-null and internal reference + iri = iri.trim(); + element[_SET_ATTRIBUTE_](refAttrName, iri + idSuffix); + if (referencedIds) { + // Add ID to referenced IDs + referencedIds[iri.substring(1)] = 1; + } + } + }); + } + element = descElements[++i]; + } + for (i = 0; i < idElements[_LENGTH_]; i++) { + idElem = idElements[i]; + // If set of referenced IDs exists, make only referenced IDs unique, + // otherwise make all IDs unique. + if (!referencedIds || referencedIds[idElem.id]) { + // Add suffix to element's ID + idElem.id += idSuffix; + changed = true; + } + } + } + // return true if SVG element has changed + return changed; + } + + + // For cached SVGs the IDs are made unique by simply replacing the already inserted unique IDs with a + // higher ID counter. This is much more performant than a call to makeIdsUnique(). + function makeIdsUniqueCached(svgString) { + return svgString.replace(ID_SUFFIX_REGEX, ID_SUFFIX + uniqueIdCounter++); + } + + + // Inject SVG by replacing the img element with the SVG element in the DOM + function inject(imgElem, svgElem, absUrl, options) { + if (svgElem) { + svgElem[_SET_ATTRIBUTE_]('data-inject-url', absUrl); + var parentNode = imgElem.parentNode; + if (parentNode) { + if (options.copyAttributes) { + copyAttributes(imgElem, svgElem); + } + // Invoke beforeInject hook if set + var beforeInject = options.beforeInject; + var injectElem = (beforeInject && beforeInject(imgElem, svgElem)) || svgElem; + // Replace img element with new element. This is the actual injection. + parentNode.replaceChild(injectElem, imgElem); + // Mark img element as injected + imgElem[__SVGINJECT] = INJECTED; + removeOnLoadAttribute(imgElem); + // Invoke afterInject hook if set + var afterInject = options.afterInject; + if (afterInject) { + afterInject(imgElem, injectElem); + } + } + } else { + svgInvalid(imgElem, options); + } + } + + + // Merges any number of options objects into a new object + function mergeOptions() { + var mergedOptions = {}; + var args = arguments; + // Iterate over all specified options objects and add all properties to the new options object + for (var i = 0; i < args[_LENGTH_]; i++) { + var argument = args[i]; + for (var key in argument) { + if (argument.hasOwnProperty(key)) { + mergedOptions[key] = argument[key]; + } + } + } + return mergedOptions; + } + + + // Adds the specified CSS to the document's element + function addStyleToHead(css) { + var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0]; + if (head) { + var style = document[_CREATE_ELEMENT_](_STYLE_); + style.type = 'text/css'; + style.appendChild(document.createTextNode(css)); + head.appendChild(style); + } + } + + + // Builds an SVG element from the specified SVG string + function buildSvgElement(svgStr, verify) { + if (verify) { + var svgDoc; + try { + // Parse the SVG string with DOMParser + svgDoc = svgStringToSvgDoc(svgStr); + } catch(e) { + return NULL; + } + if (svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) { + // DOMParser does not throw an exception, but instead puts parsererror tags in the document + return NULL; + } + return svgDoc.documentElement; + } else { + var div = document.createElement('div'); + div.innerHTML = svgStr; + return div.firstElementChild; + } + } + + + function removeOnLoadAttribute(imgElem) { + // Remove the onload attribute. Should only be used to remove the unstyled image flash protection and + // make the element visible, not for removing the event listener. + imgElem.removeAttribute('onload'); + } + + + function errorMessage(msg) { + console.error('SVGInject: ' + msg); + } + + + function fail(imgElem, status, options) { + imgElem[__SVGINJECT] = FAIL; + if (options.onFail) { + options.onFail(imgElem, status); + } else { + errorMessage(status); + } + } + + + function svgInvalid(imgElem, options) { + removeOnLoadAttribute(imgElem); + fail(imgElem, SVG_INVALID, options); + } + + + function svgNotSupported(imgElem, options) { + removeOnLoadAttribute(imgElem); + fail(imgElem, SVG_NOT_SUPPORTED, options); + } + + + function loadFail(imgElem, options) { + fail(imgElem, LOAD_FAIL, options); + } + + + function removeEventListeners(imgElem) { + imgElem.onload = NULL; + imgElem.onerror = NULL; + } + + + function imgNotSet(msg) { + errorMessage('no img element'); + } + + + function createSVGInject(globalName, options) { + var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options); + var svgLoadCache = {}; + + if (IS_SVG_SUPPORTED) { + // If the browser supports SVG, add a small stylesheet that hides the elements until + // injection is finished. This avoids showing the unstyled SVGs before style is applied. + addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}'); + } + + + /** + * SVGInject + * + * Injects the SVG specified in the `src` attribute of the specified `img` element or array of `img` + * elements. Returns a Promise object which resolves if all passed in `img` elements have either been + * injected or failed to inject (Only if a global Promise object is available like in all modern browsers + * or through a polyfill). + * + * Options: + * useCache: If set to `true` the SVG will be cached using the absolute URL. Default value is `true`. + * copyAttributes: If set to `true` the attributes will be copied from `img` to `svg`. Dfault value + * is `true`. + * makeIdsUnique: If set to `true` the ID of elements in the `` element that can be references by + * property values (for example 'clipPath') are made unique by appending "--inject-X", where X is a + * running number which increases with each injection. This is done to avoid duplicate IDs in the DOM. + * beforeLoad: Hook before SVG is loaded. The `img` element is passed as a parameter. If the hook returns + * a string it is used as the URL instead of the `img` element's `src` attribute. + * afterLoad: Hook after SVG is loaded. The loaded `svg` element and `svg` string are passed as a + * parameters. If caching is active this hook will only get called once for injected SVGs with the + * same absolute path. Changes to the `svg` element in this hook will be applied to all injected SVGs + * with the same absolute path. It's also possible to return an `svg` string or `svg` element which + * will then be used for the injection. + * beforeInject: Hook before SVG is injected. The `img` and `svg` elements are passed as parameters. If + * any html element is returned it gets injected instead of applying the default SVG injection. + * afterInject: Hook after SVG is injected. The `img` and `svg` elements are passed as parameters. + * onAllFinish: Hook after all `img` elements passed to an SVGInject() call have either been injected or + * failed to inject. + * onFail: Hook after injection fails. The `img` element and a `status` string are passed as an parameter. + * The `status` can be either `'SVG_NOT_SUPPORTED'` (the browser does not support SVG), + * `'SVG_INVALID'` (the SVG is not in a valid format) or `'LOAD_FAILED'` (loading of the SVG failed). + * + * @param {HTMLImageElement} img - an img element or an array of img elements + * @param {Object} [options] - optional parameter with [options](#options) for this injection. + */ + function SVGInject(img, options) { + options = mergeOptions(defaultOptions, options); + + var run = function(resolve) { + var allFinish = function() { + var onAllFinish = options.onAllFinish; + if (onAllFinish) { + onAllFinish(); + } + resolve && resolve(); + }; + + if (img && typeof img[_LENGTH_] != _UNDEFINED_) { + // an array like structure of img elements + var injectIndex = 0; + var injectCount = img[_LENGTH_]; + + if (injectCount == 0) { + allFinish(); + } else { + var finish = function() { + if (++injectIndex == injectCount) { + allFinish(); + } + }; + + for (var i = 0; i < injectCount; i++) { + SVGInjectElement(img[i], options, finish); + } + } + } else { + // only one img element + SVGInjectElement(img, options, allFinish); + } + }; + + // return a Promise object if globally available + return typeof Promise == _UNDEFINED_ ? run() : new Promise(run); + } + + + // Injects a single svg element. Options must be already merged with the default options. + function SVGInjectElement(imgElem, options, callback) { + if (imgElem) { + var svgInjectAttributeValue = imgElem[__SVGINJECT]; + if (!svgInjectAttributeValue) { + removeEventListeners(imgElem); + + if (!IS_SVG_SUPPORTED) { + svgNotSupported(imgElem, options); + callback(); + return; + } + // Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load + // URL path. Else use the imgElem's src attribute value. + var beforeLoad = options.beforeLoad; + var src = (beforeLoad && beforeLoad(imgElem)) || imgElem[_GET_ATTRIBUTE_]('src'); + + if (!src) { + // If no image src attribute is set do no injection. This can only be reached by using javascript + // because if no src attribute is set the onload and onerror events do not get called + if (src === '') { + loadFail(imgElem, options); + } + callback(); + return; + } + + // set array so later calls can register callbacks + var onFinishCallbacks = []; + imgElem[__SVGINJECT] = onFinishCallbacks; + + var onFinish = function() { + callback(); + onFinishCallbacks.forEach(function(onFinishCallback) { + onFinishCallback(); + }); + }; + + var absUrl = getAbsoluteUrl(src); + var useCacheOption = options.useCache; + var makeIdsUniqueOption = options.makeIdsUnique; + + var setSvgLoadCacheValue = function(val) { + if (useCacheOption) { + svgLoadCache[absUrl].forEach(function(svgLoad) { + svgLoad(val); + }); + svgLoadCache[absUrl] = val; + } + }; + + if (useCacheOption) { + var svgLoad = svgLoadCache[absUrl]; + + var handleLoadValue = function(loadValue) { + if (loadValue === LOAD_FAIL) { + loadFail(imgElem, options); + } else if (loadValue === SVG_INVALID) { + svgInvalid(imgElem, options); + } else { + var hasUniqueIds = loadValue[0]; + var svgString = loadValue[1]; + var uniqueIdsSvgString = loadValue[2]; + var svgElem; + + if (makeIdsUniqueOption) { + if (hasUniqueIds === NULL) { + // IDs for the SVG string have not been made unique before. This may happen if previous + // injection of a cached SVG have been run with the option makedIdsUnique set to false + svgElem = buildSvgElement(svgString, false); + hasUniqueIds = makeIdsUnique(svgElem, false); + + loadValue[0] = hasUniqueIds; + loadValue[2] = hasUniqueIds && svgElemToSvgString(svgElem); + } else if (hasUniqueIds) { + // Make IDs unique for already cached SVGs with better performance + svgString = makeIdsUniqueCached(uniqueIdsSvgString); + } + } + + svgElem = svgElem || buildSvgElement(svgString, false); + + inject(imgElem, svgElem, absUrl, options); + } + onFinish(); + }; + + if (typeof svgLoad != _UNDEFINED_) { + // Value for url exists in cache + if (svgLoad.isCallbackQueue) { + // Same url has been cached, but value has not been loaded yet, so add to callbacks + svgLoad.push(handleLoadValue); + } else { + handleLoadValue(svgLoad); + } + return; + } else { + var svgLoad = []; + // set property isCallbackQueue to Array to differentiate from array with cached loaded values + svgLoad.isCallbackQueue = true; + svgLoadCache[absUrl] = svgLoad; + } + } + + // Load the SVG because it is not cached or caching is disabled + loadSvg(absUrl, function(svgXml, svgString) { + // Use the XML from the XHR request if it is an instance of Document. Otherwise + // (for example of IE9), create the svg document from the svg string. + var svgElem = svgXml instanceof Document ? svgXml.documentElement : buildSvgElement(svgString, true); + + var afterLoad = options.afterLoad; + if (afterLoad) { + // Invoke afterLoad hook which may modify the SVG element. After load may also return a new + // svg element or svg string + var svgElemOrSvgString = afterLoad(svgElem, svgString) || svgElem; + if (svgElemOrSvgString) { + // Update svgElem and svgString because of modifications to the SVG element or SVG string in + // the afterLoad hook, so the modified SVG is also used for all later cached injections + var isString = typeof svgElemOrSvgString == 'string'; + svgString = isString ? svgElemOrSvgString : svgElemToSvgString(svgElem); + svgElem = isString ? buildSvgElement(svgElemOrSvgString, true) : svgElemOrSvgString; + } + } + + if (svgElem instanceof SVGElement) { + var hasUniqueIds = NULL; + if (makeIdsUniqueOption) { + hasUniqueIds = makeIdsUnique(svgElem, false); + } + + if (useCacheOption) { + var uniqueIdsSvgString = hasUniqueIds && svgElemToSvgString(svgElem); + // set an array with three entries to the load cache + setSvgLoadCacheValue([hasUniqueIds, svgString, uniqueIdsSvgString]); + } + + inject(imgElem, svgElem, absUrl, options); + } else { + svgInvalid(imgElem, options); + setSvgLoadCacheValue(SVG_INVALID); + } + onFinish(); + }, function() { + loadFail(imgElem, options); + setSvgLoadCacheValue(LOAD_FAIL); + onFinish(); + }); + } else { + if (Array.isArray(svgInjectAttributeValue)) { + // svgInjectAttributeValue is an array. Injection is not complete so register callback + svgInjectAttributeValue.push(callback); + } else { + callback(); + } + } + } else { + imgNotSet(); + } + } + + + /** + * Sets the default [options](#options) for SVGInject. + * + * @param {Object} [options] - default [options](#options) for an injection. + */ + SVGInject.setOptions = function(options) { + defaultOptions = mergeOptions(defaultOptions, options); + }; + + + // Create a new instance of SVGInject + SVGInject.create = createSVGInject; + + + /** + * Used in onerror Event of an `` element to handle cases when the loading the original src fails + * (for example if file is not found or if the browser does not support SVG). This triggers a call to the + * options onFail hook if available. The optional second parameter will be set as the new src attribute + * for the img element. + * + * @param {HTMLImageElement} img - an img element + * @param {String} [fallbackSrc] - optional parameter fallback src + */ + SVGInject.err = function(img, fallbackSrc) { + if (img) { + if (img[__SVGINJECT] != FAIL) { + removeEventListeners(img); + + if (!IS_SVG_SUPPORTED) { + svgNotSupported(img, defaultOptions); + } else { + removeOnLoadAttribute(img); + loadFail(img, defaultOptions); + } + if (fallbackSrc) { + removeOnLoadAttribute(img); + img.src = fallbackSrc; + } + } + } else { + imgNotSet(); + } + }; + + window[globalName] = SVGInject; + + return SVGInject; + } + + var SVGInjectInstance = createSVGInject('SVGInject'); + + if (typeof module == 'object' && typeof module.exports == 'object') { + module.exports = SVGInjectInstance; + } +})(window, document); \ No newline at end of file diff --git a/public/scripts/swiped-events.js b/public/lib/swiped-events.js similarity index 100% rename from public/scripts/swiped-events.js rename to public/lib/swiped-events.js diff --git a/public/scripts/toastr.js.map b/public/lib/toastr.js.map similarity index 100% rename from public/scripts/toastr.js.map rename to public/lib/toastr.js.map diff --git a/public/scripts/toastr.min.js b/public/lib/toastr.min.js similarity index 100% rename from public/scripts/toastr.min.js rename to public/lib/toastr.min.js diff --git a/public/scripts/toolcool-color-picker.js b/public/lib/toolcool-color-picker.js similarity index 100% rename from public/scripts/toolcool-color-picker.js rename to public/lib/toolcool-color-picker.js diff --git a/public/scripts/uniqolor.js b/public/lib/uniqolor.js similarity index 100% rename from public/scripts/uniqolor.js rename to public/lib/uniqolor.js diff --git a/public/script.js b/public/script.js index 934beb504..25a564c12 100644 --- a/public/script.js +++ b/public/script.js @@ -1,7 +1,7 @@ import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, } from "./scripts/RossAscends-mods.js"; import { userStatsHandler, statMesProcess } from './scripts/stats.js'; -import { encode } from "../scripts/gpt-2-3-tokenizer/mod.js"; -import { GPT3BrowserTokenizer } from "../scripts/gpt-3-tokenizer/gpt3-tokenizer.js"; +import { encode } from "../lib/gpt-2-3-tokenizer/mod.js"; +import { GPT3BrowserTokenizer } from "../lib/gpt-3-tokenizer/gpt3-tokenizer.js"; import { generateKoboldWithStreaming, kai_settings, @@ -55,6 +55,7 @@ import { renameGroupChat, importGroupChat, getGroupBlock, + getGroupChatNames, } from "./scripts/group-chats.js"; import { @@ -65,9 +66,6 @@ import { power_user, pygmalion_options, tokenizers, - formatInstructModeChat, - formatInstructStoryString, - formatInstructModePrompt, persona_description_positions, loadMovingUIState, getCustomStoppingStrings, @@ -157,7 +155,7 @@ import { secret_state, writeSecret } from "./scripts/secrets.js"; -import { EventEmitter } from './scripts/eventemitter.js'; +import { EventEmitter } from './lib/eventemitter.js'; import { markdownExclusionExt } from "./scripts/showdown-exclusion.js"; import { NOTE_MODULE_NAME, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from "./scripts/authors-note.js"; import { deviceInfo } from "./scripts/RossAscends-mods.js"; @@ -165,6 +163,13 @@ import { registerPromptManagerMigration } from "./scripts/PromptManager.js"; import { getRegexedString, regex_placement } from "./scripts/extensions/regex/engine.js"; import { FILTER_TYPES, FilterHelper } from "./scripts/filters.js"; import { getCfgPrompt, getGuidanceScale } from "./scripts/extensions/cfg/util.js"; +import { + formatInstructModeChat, + formatInstructModePrompt, + formatInstructModeExamples, + getInstructStoppingSequences, + autoSelectInstructPreset, +} from "./scripts/instruct-mode.js"; //exporting functions and vars for mods export { @@ -330,7 +335,6 @@ let scrollLock = false; const durationSaveEdit = 1000; const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit); export const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit); -const getStatusDebounced = debounce(() => getStatus(), 300_000); const saveChatDebounced = debounce(() => saveChatConditional(), durationSaveEdit); const system_message_types = { @@ -752,7 +756,7 @@ let create_save = { }; //animation right menu -let animation_duration = 250; +let animation_duration = 125; let animation_easing = "ease-in-out"; let popup_type = ""; let bg_file_for_del = ""; @@ -884,10 +888,6 @@ async function getStatus() { const hordeStatus = await checkHordeStatus(); online_status = hordeStatus ? 'Connected' : 'no_connection'; resultCheckStatus(); - - if (online_status !== "no_connection") { - getStatusDebounced(); - } } catch { online_status = "no_connection"; @@ -916,6 +916,10 @@ async function getStatus() { if (online_status == undefined) { online_status = "no_connection"; } + + // Determine instruct mode preset + autoSelectInstructPreset(online_status); + if ((online_status.toLowerCase().indexOf("pygmalion") != -1 && power_user.pygmalion_formatting == pygmalion_options.AUTO) || (online_status !== "no_connection" && power_user.pygmalion_formatting == pygmalion_options.ENABLED)) { is_pygmalion = true; @@ -937,9 +941,6 @@ async function getStatus() { //console.log(online_status); resultCheckStatus(); - if (online_status !== "no_connection") { - getStatusDebounced(); - } }, error: function (jqXHR, exception) { console.log(exception); @@ -1042,7 +1043,7 @@ async function printCharacters(fullRefresh = false) { $("#rm_print_characters_pagination").pagination({ dataSource: getEntitiesList({ doFilter: true }), pageSize: Number(localStorage.getItem(storageKey)) || 50, - sizeChangerOptions: [25, 50, 100, 250, 500, 1000], + sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000], pageRange: 1, position: 'top', showPageNumbers: false, @@ -1090,6 +1091,30 @@ export function getEntitiesList({ doFilter } = {}) { return entities; } +async function getOneCharacter(avatarUrl) { + const response = await fetch("/getonecharacter", { + method: "POST", + headers: getRequestHeaders(), + body: JSON.stringify({ + avatar_url: avatarUrl, + }), + }); + + if (response.ok) { + const getData = await response.json(); + getData['name'] = DOMPurify.sanitize(getData['name']); + getData['chat'] = String(getData['chat']); + + const indexOf = characters.findIndex(x => x.avatar === avatarUrl); + + if (indexOf !== -1) { + characters[indexOf] = getData; + } else { + toastr.error(`Character ${avatarUrl} not found in the list`, "Error", { timeOut: 5000, preventDuplicates: true }); + } + } +} + async function getCharacters() { var response = await fetch("/getcharacters", { method: "POST", @@ -1423,25 +1448,25 @@ function insertSVGIcon(mes, extra) { modelName = extra.api; } - // Fetch the SVG based on the modelName - $.get(`/img/${modelName}.svg`, function (data) { - // Extract the SVG content from the XML data - let svg = $(data).find('svg'); - - // Add classes for styling and identification - svg.addClass('icon-svg timestamp-icon'); + const image = new Image(); + // Add classes for styling and identification + image.classList.add('icon-svg', 'timestamp-icon'); + image.src = `/img/${modelName}.svg`; + image.onload = async function () { // Check if an SVG already exists adjacent to the timestamp let existingSVG = mes.find('.timestamp').next('.timestamp-icon'); if (existingSVG.length) { // Replace existing SVG - existingSVG.replaceWith(svg); + existingSVG.replaceWith(image); } else { // Append the new SVG if none exists - mes.find('.timestamp').after(svg); + mes.find('.timestamp').after(image); } - }); + + await SVGInject(this); + }; } @@ -1581,8 +1606,18 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true mes.is_user, ); const bias = messageFormatting(mes.extra?.bias ?? ""); - const bookmarkLink = mes?.extra?.bookmark_link ?? ''; + let bookmarkLink = mes?.extra?.bookmark_link ?? ''; + // Verify bookmarked chat still exists + // Cohee: Commented out for now. I'm worried of performance issues. + /*if (bookmarkLink !== '') { + let chat_names = selected_group + ? getGroupChatNames(selected_group) + : Object.values(getPastCharacterChats()).map(({ file_name }) => file_name); + if (!chat_names.includes(bookmarkLink)) { + bookmarkLink = '' + } + }*/ let params = { mesId: count_view_mes, characterName: characterName, @@ -1746,7 +1781,6 @@ function scrollChatToBottom() { function substituteParams(content, _name1, _name2, _original, _group) { _name1 = _name1 ?? name1; _name2 = _name2 ?? name2; - _original = _original || ''; _group = _group ?? name2; if (!content) { @@ -1880,27 +1914,7 @@ function getStoppingStrings(isImpersonate, addSpace) { } } - function addInstructSequence(sequence) { - // Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string - // But it's a problem for Metharme which doesn't use newlines to separate them. - const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s; - // Sequence must be a non-empty string - if (typeof sequence === 'string' && sequence.length > 0) { - // If sequence is just a whitespace or newline - we don't want to make it a stopping string - // User can always add it as a custom stop string if really needed - if (sequence.trim().length > 0) { - const wrappedSequence = wrap(sequence); - // Need to respect "insert macro" setting - const stopString = power_user.instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence; - result.push(stopString); - } - } - } - - if (power_user.instruct.enabled) { - addInstructSequence(power_user.instruct.input_sequence); - addInstructSequence(power_user.instruct.output_sequence); - } + result.push(...getInstructStoppingSequences()); if (power_user.custom_stopping_strings) { const customStoppingStrings = getCustomStoppingStrings(); @@ -2042,9 +2056,8 @@ function getPersonaDescription(storyString) { switch (power_user.persona_description_position) { case persona_description_positions.BEFORE_CHAR: - return `${substituteParams(power_user.persona_description)}\n${storyString}`; case persona_description_positions.AFTER_CHAR: - return `${storyString}${substituteParams(power_user.persona_description)}\n`; + return storyString; default: if (shouldWIAddPrompt) { const originalAN = extension_prompts[NOTE_MODULE_NAME].value @@ -2098,12 +2111,14 @@ function baseChatReplace(value, name1, name2) { if (power_user.collapse_newlines) { value = collapseNewlines(value); } + + value = value.replace(/\r/g, ''); } return value; } function isStreamingEnabled() { - return ((main_api == 'openai' && oai_settings.stream_openai && oai_settings.chat_completion_source !== chat_completion_sources.SCALE) + return ((main_api == 'openai' && oai_settings.stream_openai && oai_settings.chat_completion_source !== chat_completion_sources.SCALE && oai_settings.chat_completion_source !== chat_completion_sources.AI21) || (main_api == 'kobold' && kai_settings.streaming_kobold && kai_settings.can_use_streaming) || (main_api == 'novel' && nai_settings.streaming_novel) || (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming)) @@ -2349,10 +2364,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, abortController = new AbortController(); } - if (main_api == 'novel' && quiet_prompt) { - quiet_prompt = adjustNovelInstructionPrompt(quiet_prompt); - } - // OpenAI doesn't need instruct mode. Use OAI main prompt instead. const isInstruct = power_user.instruct.enabled && main_api !== 'openai'; const isImpersonate = type == "impersonate"; @@ -2441,6 +2452,11 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, } } + if (quiet_prompt) { + quiet_prompt = substituteParams(quiet_prompt); + quiet_prompt = main_api == 'novel' ? adjustNovelInstructionPrompt(quiet_prompt) : quiet_prompt; + } + if (true === dryRun || (online_status != 'no_connection' && this_chid != undefined && this_chid !== 'invalid-safety-id')) { let textareaText; @@ -2498,10 +2514,15 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, const scenarioText = chat_metadata['scenario'] || characters[this_chid].scenario; let charDescription = baseChatReplace(characters[this_chid].description.trim(), name1, name2); let charPersonality = baseChatReplace(characters[this_chid].personality.trim(), name1, name2); + let personaDescription = baseChatReplace(power_user.persona_description.trim(), name1, name2); let Scenario = baseChatReplace(scenarioText.trim(), name1, name2); let mesExamples = baseChatReplace(characters[this_chid].mes_example.trim(), name1, name2); - let systemPrompt = baseChatReplace(characters[this_chid].data?.system_prompt?.trim(), name1, name2); - let jailbreakPrompt = baseChatReplace(characters[this_chid].data?.post_history_instructions?.trim(), name1, name2); + let systemPrompt = power_user.prefer_character_prompt ? baseChatReplace(characters[this_chid].data?.system_prompt?.trim(), name1, name2) : ''; + let jailbreakPrompt = power_user.prefer_character_jailbreak ? baseChatReplace(characters[this_chid].data?.post_history_instructions?.trim(), name1, name2) : ''; + + if (isInstruct) { + systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : baseChatReplace(power_user.instruct.system_prompt, name1, name2); + } // Parse example messages if (!mesExamples.startsWith('')) { @@ -2510,11 +2531,17 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, if (mesExamples.replace(//gi, '').trim().length === 0) { mesExamples = ''; } + if (mesExamples && isInstruct) { + mesExamples = formatInstructModeExamples(mesExamples, name1, name2) + } const exampleSeparator = power_user.context.example_separator ? `${power_user.context.example_separator}\n` : ''; const blockHeading = main_api === 'openai' ? '\n' : exampleSeparator; let mesExamplesArray = mesExamples.split(//gi).slice(1).map(block => `${blockHeading}${block.trim()}\n`); + if (power_user.strip_examples) + mesExamplesArray = [] + // First message in fresh 1-on-1 chat reacts to user/character settings changes if (chat.length) { chat[0].mes = substituteParams(chat[0].mes); @@ -2541,7 +2568,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, const storyStringParams = { description: charDescription, personality: charPersonality, + persona: personaDescription, scenario: Scenario, + system: isInstruct ? systemPrompt : '', char: name2, user: name1, }; @@ -2610,11 +2639,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, storyString = worldInfoBefore + storyString + worldInfoAfter; } - // Format the instruction string - if (isInstruct) { - storyString = formatInstructStoryString(storyString, systemPrompt); - } - if (main_api === 'openai') { message_already_generated = ''; // OpenAI doesn't have multigen setOpenAIMessages(coreChat); @@ -3006,8 +3030,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, bias: promptBias, type: type, quietPrompt: quiet_prompt, - jailbreakPrompt: jailbreakPrompt, cyclePrompt: cyclePrompt, + systemPromptOverride: systemPrompt, + jailbreakPromptOverride: jailbreakPrompt, }, dryRun); generate_data = { prompt: prompt }; @@ -4042,10 +4067,28 @@ function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete } } if (isInstruct && power_user.instruct.input_sequence && isImpersonate) { - getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, ''); + //getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, ''); + power_user.instruct.input_sequence.split('\n') + .filter(line => line.trim() !== '') + .forEach(line => { + getMessage = getMessage.replaceAll(line, ''); + }); } if (isInstruct && power_user.instruct.output_sequence && !isImpersonate) { - getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, ''); + //getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, ''); + power_user.instruct.output_sequence.split('\n') + .filter(line => line.trim() !== '') + .forEach(line => { + getMessage = getMessage.replaceAll(line, ''); + }); + } + if (isInstruct && power_user.instruct.last_output_sequence && !isImpersonate) { + //getMessage = getMessage.replaceAll(power_user.instruct.last_output_sequence, ''); + power_user.instruct.last_output_sequence.split('\n') + .filter(line => line.trim() !== '') + .forEach(line => { + getMessage = getMessage.replaceAll(line, ''); + }); } // clean-up group message from excessive generations if (selected_group) { @@ -4571,10 +4614,9 @@ async function getChat() { chat_create_date = humanizedDateTime(); } await getChatResult(); - await saveChat(); + saveChatDebounced(); eventSource.emit('chatLoaded', { detail: { id: this_chid, character: characters[this_chid] } }); - setTimeout(function () { $('#send_textarea').click(); $('#send_textarea').focus(); @@ -4626,7 +4668,7 @@ async function openCharacterChat(file_name) { chat_metadata = {}; await getChat(); $("#selected_chat_pole").val(file_name); - $("#create_button").click(); + await createOrEditCharacter(); } ////////// OPTIMZED MAIN API CHANGE FUNCTION //////////// @@ -4745,6 +4787,7 @@ function changeMainAPI() { case chat_completion_sources.WINDOWAI: case chat_completion_sources.CLAUDE: case chat_completion_sources.OPENAI: + case chat_completion_sources.AI21: default: setupChatCompletionPromptManager(oai_settings); break; @@ -5406,9 +5449,8 @@ async function getSettings(type) { setWorldInfoSettings(settings.world_info_settings ?? settings, data); api_server_textgenerationwebui = settings.api_server_textgenerationwebui; - $("#textgenerationwebui_api_url_text").val( - api_server_textgenerationwebui - ); + $("#textgenerationwebui_api_url_text").val(api_server_textgenerationwebui); + $("#mancer_api_url_text").val(api_server_textgenerationwebui); api_use_mancer_webui = settings.api_use_mancer_webui $('#use-mancer-api-checkbox').prop("checked", api_use_mancer_webui); $('#use-mancer-api-checkbox').trigger("change"); @@ -5630,6 +5672,66 @@ async function messageEditDone(div) { await saveChatConditional(); } +/** + * Fetches the chat content for each chat file from the server and compiles them into a dictionary. + * The function iterates over a provided list of chat metadata and requests the actual chat content + * for each chat, either as an individual chat or a group chat based on the context. + * + * @param {Array} data - An array containing metadata about each chat such as file_name. + * @param {boolean} isGroupChat - A flag indicating if the chat is a group chat. + * @returns {Object} chat_dict - A dictionary where each key is a file_name and the value is the + * corresponding chat content fetched from the server. + */ +export async function getChatsFromFiles(data, isGroupChat) { + const context = getContext(); + let chat_dict = {}; + let chat_list = Object.values(data).sort((a, b) => a["file_name"].localeCompare(b["file_name"])).reverse(); + + for (const { file_name } of chat_list) { + try { + const endpoint = isGroupChat ? '/getgroupchat' : '/getchat'; + const requestBody = isGroupChat + ? JSON.stringify({ id: file_name }) + : JSON.stringify({ + ch_name: characters[context.characterId].name, + file_name: file_name.replace('.jsonl', ''), + avatar_url: characters[context.characterId].avatar + }); + + const chatResponse = await fetch(endpoint, { + method: 'POST', + headers: getRequestHeaders(), + body: requestBody, + cache: 'no-cache', + }); + + if (!chatResponse.ok) { + continue; + } + + const currentChat = await chatResponse.json(); + if (!isGroupChat) { + // remove the first message, which is metadata, only for individual chats + currentChat.shift(); + } + chat_dict[file_name] = currentChat; + + } catch (error) { + console.error(error); + } + } + + return chat_dict; +} + +/** + * Fetches the metadata of all past chats related to a specific character based on its avatar URL. + * The function sends a POST request to the server to retrieve all chats for the character. It then + * processes the received data, sorts it by the file name, and returns the sorted data. + * + * @returns {Array} - An array containing metadata of all past chats of the character, sorted + * in descending order by file name. Returns `undefined` if the fetch request is unsuccessful. + */ async function getPastCharacterChats() { const response = await fetch("/getallchatsofcharacter", { method: 'POST', @@ -5647,6 +5749,12 @@ async function getPastCharacterChats() { return data; } +/** + * Displays the past chats for a character or a group based on the selected context. + * The function first fetches the chats, processes them, and then displays them in + * the HTML. It also has a built-in search functionality that allows filtering the + * displayed chats based on a search query. + */ export async function displayPastChats() { $("#select_chat_div").empty(); @@ -5655,45 +5763,69 @@ export async function displayPastChats() { const currentChat = selected_group ? group?.chat_id : characters[this_chid]["chat"]; const displayName = selected_group ? group?.name : characters[this_chid].name; const avatarImg = selected_group ? group?.avatar_url : getThumbnailUrl('avatar', characters[this_chid]['avatar']); - + const rawChats = await getChatsFromFiles(data, selected_group); // Sort by last message date descending data.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes))); - + console.log(data); $("#load_select_chat_div").css("display", "none"); $("#ChatHistoryCharName").text(displayName); - for (const key in data) { - let strlen = 300; - let mes = data[key]["mes"]; - if (mes !== undefined) { - if (mes.length > strlen) { - mes = "..." + mes.substring(mes.length - strlen); - } - const chat_items = data[key]["chat_items"]; - const file_size = data[key]["file_size"]; - const fileName = data[key]['file_name']; - const timestamp = timestampToMoment(data[key]['last_mes']).format('LL LT'); - const template = $('#past_chat_template .select_chat_block_wrapper').clone(); - template.find('.select_chat_block').attr('file_name', fileName); - template.find('.avatar img').attr('src', avatarImg); - template.find('.select_chat_block_filename').text(fileName); - template.find('.chat_file_size').text(" (" + file_size + ")"); - template.find('.chat_messages_num').text(" (" + chat_items + " messages)"); - template.find('.select_chat_block_mes').text(mes); - template.find('.PastChat_cross').attr('file_name', fileName); - template.find('.chat_messages_date').text(timestamp); + const displayChats = (searchQuery) => { + $("#select_chat_div").empty(); // Clear the current chats before appending filtered chats - if (selected_group) { - template.find('.avatar img').replaceWith(getGroupAvatar(group)); - } + const filteredData = data.filter(chat => { + const fileName = chat['file_name']; + const chatContent = rawChats[fileName]; - $("#select_chat_div").append(template); + return chatContent && Object.values(chatContent).some(message => message.mes.toLowerCase().includes(searchQuery.toLowerCase())); + }); - if (currentChat === fileName.toString().replace(".jsonl", "")) { - $("#select_chat_div").find(".select_chat_block:last").attr("highlight", true); + console.log(filteredData); + for (const key in filteredData) { + let strlen = 300; + let mes = filteredData[key]["mes"]; + + if (mes !== undefined) { + if (mes.length > strlen) { + mes = "..." + mes.substring(mes.length - strlen); + } + const chat_items = data[key]["chat_items"]; + const file_size = data[key]["file_size"]; + const fileName = data[key]['file_name']; + const timestamp = timestampToMoment(data[key]['last_mes']).format('LL LT'); + const template = $('#past_chat_template .select_chat_block_wrapper').clone(); + template.find('.select_chat_block').attr('file_name', fileName); + template.find('.avatar img').attr('src', avatarImg); + template.find('.select_chat_block_filename').text(fileName); + template.find('.chat_file_size').text(" (" + file_size + ")"); + template.find('.chat_messages_num').text(" (" + chat_items + " messages)"); + template.find('.select_chat_block_mes').text(mes); + template.find('.PastChat_cross').attr('file_name', fileName); + template.find('.chat_messages_date').text(timestamp); + + if (selected_group) { + template.find('.avatar img').replaceWith(getGroupAvatar(group)); + } + + $("#select_chat_div").append(template); + + if (currentChat === fileName.toString().replace(".jsonl", "")) { + $("#select_chat_div").find(".select_chat_block:last").attr("highlight", true); + } } } } + displayChats(''); // Display all by default + + const debouncedDisplay = debounce((searchQuery) => { + displayChats(searchQuery); + }, 300); + + // Define the search input listener + $("#select_chat_search").on("input", function () { + const searchQuery = $(this).val(); + debouncedDisplay(searchQuery); + }); } //************************************************************ @@ -5736,11 +5868,11 @@ async function getStatusNovel() { function selectRightMenuWithAnimation(selectedMenuId) { const displayModes = { - 'rm_info_block': 'flex', 'rm_group_chats_block': 'flex', 'rm_api_block': 'grid', 'rm_characters_block': 'flex', }; + $('#hideCharPanelAvatarButton').toggle(selectedMenuId === 'rm_ch_create_block'); document.querySelectorAll('#right-nav-panel .right_menu').forEach((menu) => { $(menu).css('display', 'none'); @@ -5794,7 +5926,6 @@ function select_rm_info(type, charId, previousCharId = null) { toastr.success(`Character Imported: ${displayName}`); } - getCharacters(); selectRightMenuWithAnimation('rm_characters_block'); setTimeout(function () { @@ -6202,10 +6333,10 @@ function hideSwipeButtons() { async function saveMetadata() { if (selected_group) { - await editGroup(selected_group, true, false); + await editGroup(selected_group, false, false); } else { - await saveChat(); + saveChatDebounced(); } } @@ -6609,12 +6740,8 @@ async function createOrEditCharacter(e) { createTagMapFromList("#tagList", html); await getCharacters(); - $("#rm_info_block").transition({ opacity: 0, duration: 0 }); - var $prev_img = $("#avatar_div_div").clone(); - $("#rm_info_avatar").append($prev_img); select_rm_info(`char_create`, html, oldSelectedChar); - $("#rm_info_block").transition({ opacity: 1.0, duration: 2000 }); crop_data = undefined; }, error: function (jqXHR, exception) { @@ -6703,7 +6830,8 @@ async function createOrEditCharacter(e) { } } $("#create_button").removeAttr("disabled"); - await getCharacters(); + + await getOneCharacter(formData.get('avatar_url')); $("#add_avatar_button").replaceWith( $("#add_avatar_button").val("").clone(true) @@ -7098,6 +7226,11 @@ function connectAPISlash(_, text) { source: 'openrouter', button: '#api_button_openai', }, + 'ai21': { + selected: 'openai', + source: 'ai21', + button: '#api_button_openai', + } }; const apiConfig = apiMap[text]; @@ -7160,12 +7293,6 @@ function importCharacter(file) { if (data.file_name !== undefined) { $('#character_search_bar').val('').trigger('input'); - $("#rm_info_block").transition({ opacity: 0, duration: 0 }); - var $prev_img = $("#avatar_div_div").clone(); - $prev_img - .children("img") - .attr("src", "characters/" + data.file_name + ".png"); - $("#rm_info_avatar").append($prev_img); let oldSelectedChar = null; if (this_chid != undefined && this_chid != "invalid-safety-id") { @@ -7180,7 +7307,6 @@ function importCharacter(file) { let importedCharacter = currentContext.characters.find(character => character.avatar === avatarFileName); await importTags(importedCharacter); } - $("#rm_info_block").transition({ opacity: 1, duration: 1000 }); } }, error: function (jqXHR, exception) { @@ -7323,7 +7449,7 @@ $(document).ready(function () { } registerSlashCommand('dupe', DupeChar, [], "– duplicates the currently selected character", true, true); - registerSlashCommand('api', connectAPISlash, [], "(kobold, horde, novel, ooba, oai, claude, windowai) – connect to an API", true, true); + registerSlashCommand('api', connectAPISlash, [], "(kobold, horde, novel, ooba, oai, claude, windowai, ai21) – connect to an API", true, true); registerSlashCommand('impersonate', doImpersonate, ['imp'], "- calls an impersonation response", true, true); registerSlashCommand('delchat', doDeleteChat, [], "- deletes the current chat", true, true); registerSlashCommand('closechat', doCloseChat, [], "- closes the current chat", true, true); @@ -7437,7 +7563,7 @@ $(document).ready(function () { $("#character_search_bar").val("").trigger("input"); }); - $(document).on("click", ".character_select", function() { + $(document).on("click", ".character_select", function () { const id = $(this).attr("chid"); selectCharacterById(id); }); @@ -7648,7 +7774,7 @@ $(document).ready(function () { setTimeout(function () { $("#option_select_chat").click(); $("#options").hide(); - }, 200); + }, 2000); } if (popup_type == "del_ch") { const deleteChats = !!$("#del_char_checkbox").prop("checked"); @@ -7747,11 +7873,6 @@ $(document).ready(function () { ); }); - $("#rm_info_button").on('click', function () { - $("#rm_info_avatar").html(""); - select_rm_characters(); - }); - //////// OPTIMIZED ALL CHAR CREATION/EDITING TEXTAREA LISTENERS /////////////// $("#character_name_pole").on("input", function () { @@ -7926,7 +8047,9 @@ $(document).ready(function () { $("#use-mancer-api-checkbox").on("change", function (e) { const enabled = $("#use-mancer-api-checkbox").prop("checked"); - $("#mancer-api-ui").toggle(enabled); + $("#mancer_api_subpanel").toggle(enabled); + $("#tgwebui_api_subpanel").toggle(!enabled); + api_use_mancer_webui = enabled; saveSettingsDebounced(); getStatus(); @@ -7934,8 +8057,9 @@ $(document).ready(function () { $("#api_button_textgenerationwebui").click(async function (e) { e.stopPropagation(); - if ($("#textgenerationwebui_api_url_text").val() != "") { - let value = formatTextGenURL($("#textgenerationwebui_api_url_text").val().trim(), api_use_mancer_webui); + const url_source = api_use_mancer_webui ? "#mancer_api_url_text" : "#textgenerationwebui_api_url_text"; + if ($(url_source).val() != "") { + let value = formatTextGenURL($(url_source).val().trim(), api_use_mancer_webui); if (!value) { callPopup("Please enter a valid URL.
WebUI URLs should end with /api
Enable 'Relaxed API URLs' to allow other paths.", 'text'); return; @@ -7946,9 +8070,13 @@ $(document).ready(function () { await writeSecret(SECRET_KEYS.MANCER, mancer_key); } - $("#textgenerationwebui_api_url_text").val(value); + $(url_source).val(value); $("#api_loading_textgenerationwebui").css("display", "inline-block"); $("#api_button_textgenerationwebui").css("display", "none"); + + if (api_use_mancer_webui) { + textgenerationwebui_settings.streaming_url = value.replace("http", "ws") + "/v1/stream"; + } api_server_textgenerationwebui = value; main_api = "textgenerationwebui"; saveSettingsDebounced(); @@ -8501,8 +8629,10 @@ $(document).ready(function () { $(document).on("click", ".mes_edit_delete", async function (event, customData) { const fromSlashCommand = customData?.fromSlashCommand || false; + const swipeExists = (!chat[this_edit_mes_id].swipes || chat[this_edit_mes_id].swipes.length <= 1 || chat.is_user || parseInt(this_edit_mes_id) !== chat.length - 1); if (power_user.confirm_message_delete && fromSlashCommand !== true) { - const confirmation = await callPopup("Are you sure you want to delete this message?", 'confirm'); + const confirmation = swipeExists ? await callPopup("Are you sure you want to delete this message?", 'confirm') + : await callPopup("

Delete this...

", 'confirm') if (!confirmation) { return; } @@ -8514,10 +8644,21 @@ $(document).ready(function () { return; } - chat.splice(this_edit_mes_id, 1); + if ($('#del_type').val() === 'swipe') { + const swipe_id = chat[this_edit_mes_id]['swipe_id']; + chat[this_edit_mes_id]['swipes'].splice(swipe_id, 1); + if (swipe_id > 0) { + $('.swipe_left:last').click(); + } else { + $('.swipe_right:last').click() + } + } else { + chat.splice(this_edit_mes_id, 1); + mes.remove(); + count_view_mes--; + } + this_edit_mes_id = undefined; - mes.remove(); - count_view_mes--; updateViewMessageIds(); saveChatConditional(); @@ -9155,8 +9296,7 @@ $(document).ready(function () { doCharListDisplaySwitch(); }); - $("#hideCharPanelAvatarButton").on('click', () => { + $("#hideCharPanelAvatarButton").hide().on('click', () => { $('#avatar-and-name-block').slideToggle() - }) - + }); }); diff --git a/public/scripts/PromptManager.js b/public/scripts/PromptManager.js index cdd771358..9b2e6d99a 100644 --- a/public/scripts/PromptManager.js +++ b/public/scripts/PromptManager.js @@ -1,8 +1,10 @@ -import {callPopup, event_types, eventSource, is_send_press, main_api, substituteParams} from "../script.js"; +"use strict"; + +import { callPopup, event_types, eventSource, is_send_press, main_api, substituteParams } from "../script.js"; import { is_group_generating } from "./group-chats.js"; -import {TokenHandler} from "./openai.js"; -import {power_user} from "./power-user.js"; -import { debounce, getSortableDelay, waitUntilCondition } from "./utils.js"; +import { TokenHandler } from "./openai.js"; +import { power_user } from "./power-user.js"; +import { debounce, waitUntilCondition } from "./utils.js"; function debouncePromise(func, delay) { let timeoutId; @@ -70,7 +72,7 @@ class Prompt { * @param {string} param0.name - The name of the prompt. * @param {boolean} param0.system_prompt - Indicates if the prompt is a system prompt. */ - constructor({identifier, role, content, name, system_prompt} = {}) { + constructor({ identifier, role, content, name, system_prompt } = {}) { this.identifier = identifier; this.role = role; this.content = content; @@ -101,8 +103,8 @@ class PromptCollection { * @throws Will throw an error if one or more instances are not of the Prompt class. */ checkPromptInstance(...prompts) { - for(let prompt of prompts) { - if(!(prompt instanceof Prompt)) { + for (let prompt of prompts) { + if (!(prompt instanceof Prompt)) { throw new Error('Only Prompt instances can be added to PromptCollection'); } } @@ -168,7 +170,11 @@ function PromptManagerModule() { listIdentifier: '', listItemTemplateIdentifier: '', toggleDisabled: [], - draggable: true, + promptOrder: { + strategy: 'global', + dummyId: 100000 + }, + sortableDelay: 30, warningTokenThreshold: 1500, dangerTokenThreshold: 500, defaultPrompts: { @@ -246,7 +252,7 @@ function PromptManagerModule() { this.handleCharacterExport = () => { }; /** Character reset button click*/ - this.handleCharacterReset = () => {}; + this.handleCharacterReset = () => { }; /** Debounced version of render */ this.renderDebounced = debounce(this.render.bind(this), 1000); @@ -267,6 +273,8 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti this.serviceSettings = serviceSettings; this.containerElement = document.getElementById(this.configuration.containerIdentifier); + if ('global' === this.configuration.promptOrder.strategy) this.activeCharacter = {id: this.configuration.promptOrder.dummyId}; + this.sanitizeServiceSettings(); // Enable and disable prompts @@ -378,7 +386,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value; const prompt = this.getPromptById(promptID); - if (prompt){ + if (prompt) { this.appendPrompt(prompt, this.activeCharacter); this.saveServiceSettings().then(() => this.render()); } @@ -386,7 +394,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti // Delete selected prompt from list form and close edit form this.handleDeletePrompt = (event) => { - const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value; + const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value; const prompt = this.getPromptById(promptID); if (prompt && true === this.isPromptDeletionAllowed(prompt)) { @@ -416,12 +424,26 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti // Export all user prompts this.handleFullExport = () => { - const exportPrompts = this.serviceSettings.prompts.reduce((userPrompts, prompt) => { + const prompts = this.serviceSettings.prompts.reduce((userPrompts, prompt) => { if (false === prompt.system_prompt && false === prompt.marker) userPrompts.push(prompt); return userPrompts; }, []); - this.export({prompts: exportPrompts}, 'full', 'st-prompts'); + let promptOrder = []; + if ('global' === this.configuration.promptOrder.strategy) { + promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId }); + } else if ('character' === this.configuration.promptOrder.strategy) { + promptOrder = []; + } else { + throw new Error('Prompt order strategy not supported.') + } + + const exportPrompts = { + prompts: prompts, + prompt_order: promptOrder + } + + this.export(exportPrompts, 'full', 'st-prompts'); } // Export user prompts and order for this character @@ -472,10 +494,10 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti }; reader.readAsText(file); - }); + }); - fileOpener.click(); - }); + fileOpener.click(); + }); } // Restore default state of a characters prompt order @@ -538,7 +560,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_save').addEventListener('click', this.handleSavePrompt); document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_reset').addEventListener('click', this.handleResetPrompt); - const closeAndClearPopup = () => { + const closeAndClearPopup = () => { this.hidePopup(); this.clearEditForm(); this.clearInspectForm(); @@ -554,7 +576,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti this.saveServiceSettings().then(() => { this.hidePopup(); this.clearEditForm(); - this.renderDebounced() + this.renderDebounced(); }); }); @@ -572,7 +594,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti PromptManagerModule.prototype.render = function (afterTryGenerate = true) { if (main_api !== 'openai') return; - if (null === this.activeCharacter) return; + if ('character' === this.configuration.promptOrder.strategy && null === this.activeCharacter) return; this.error = null; waitUntilCondition(() => !is_send_press && !is_group_generating, 1024 * 1024, 100).then(() => { @@ -586,6 +608,11 @@ PromptManagerModule.prototype.render = function (afterTryGenerate = true) { this.renderPromptManagerListItems() this.makeDraggable(); this.profileEnd('render'); + }).catch(error => { + this.log('Error caught during render: ' + error); + this.renderPromptManager(); + this.renderPromptManagerListItems() + this.makeDraggable(); }); } else { // Executed during live communication @@ -634,7 +661,7 @@ PromptManagerModule.prototype.updatePrompts = function (prompts) { }) } -PromptManagerModule.prototype.getTokenHandler = function() { +PromptManagerModule.prototype.getTokenHandler = function () { return this.tokenHandler; } @@ -648,7 +675,7 @@ PromptManagerModule.prototype.appendPrompt = function (prompt, character) { const promptOrder = this.getPromptOrderForCharacter(character); const index = promptOrder.findIndex(entry => entry.identifier === prompt.identifier); - if (-1 === index) promptOrder.push({identifier: prompt.identifier, enabled: false}); + if (-1 === index) promptOrder.push({ identifier: prompt.identifier, enabled: false }); } /** @@ -694,6 +721,13 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () { this.serviceSettings.prompts = this.serviceSettings.prompts ?? []; this.serviceSettings.prompt_order = this.serviceSettings.prompt_order ?? []; + if ('global' === this.configuration.promptOrder.strategy) { + const dummyCharacter = { id: this.configuration.promptOrder.dummyId }; + const promptOrder = this.getPromptOrderForCharacter(dummyCharacter); + + if (0 === promptOrder.length) this.addPromptOrderForCharacter(dummyCharacter, promptManagerDefaultPromptOrder); + } + // Check whether the referenced prompts are present. this.serviceSettings.prompts.length === 0 ? this.setPrompts(chatCompletionDefaultPrompts.prompts) @@ -704,11 +738,11 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () { if (this.activeCharacter) { const promptReferences = this.getPromptOrderForCharacter(this.activeCharacter); - for(let i = promptReferences.length - 1; i >= 0; i--) { - const reference = promptReferences[i]; - if(-1 === this.serviceSettings.prompts.findIndex(prompt => prompt.identifier === reference.identifier)) { + for (let i = promptReferences.length - 1; i >= 0; i--) { + const reference = promptReferences[i]; + if (-1 === this.serviceSettings.prompts.findIndex(prompt => prompt.identifier === reference.identifier)) { promptReferences.splice(i, 1); - this.log('Removed unused reference: ' + reference.identifier); + this.log('Removed unused reference: ' + reference.identifier); } } } @@ -720,11 +754,11 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () { * * @param prompts */ -PromptManagerModule.prototype.checkForMissingPrompts = function(prompts) { - const defaultPromptIdentifiers = chatCompletionDefaultPrompts.prompts.reduce((list, prompt) => { list.push(prompt.identifier); return list;}, []); +PromptManagerModule.prototype.checkForMissingPrompts = function (prompts) { + const defaultPromptIdentifiers = chatCompletionDefaultPrompts.prompts.reduce((list, prompt) => { list.push(prompt.identifier); return list; }, []); const missingIdentifiers = defaultPromptIdentifiers.filter(identifier => - !prompts.some(prompt =>prompt.identifier === identifier) + !prompts.some(prompt => prompt.identifier === identifier) ); missingIdentifiers.forEach(identifier => { @@ -775,9 +809,10 @@ PromptManagerModule.prototype.isPromptToggleAllowed = function (prompt) { /** * Handle the deletion of a character by removing their prompt list and nullifying the active character if it was the one deleted. * @param {object} event - The event object containing the character's ID. - * @returns boolean + * @returns void */ PromptManagerModule.prototype.handleCharacterDeleted = function (event) { + if ('global' === this.configuration.promptOrder.strategy) return; this.removePromptOrderForCharacter(this.activeCharacter); if (this.activeCharacter.id === event.detail.id) this.activeCharacter = null; } @@ -788,12 +823,19 @@ PromptManagerModule.prototype.handleCharacterDeleted = function (event) { * @returns {void} */ PromptManagerModule.prototype.handleCharacterSelected = function (event) { - this.activeCharacter = {id: event.detail.id, ...event.detail.character}; - const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); + if ('global' === this.configuration.promptOrder.strategy) { + this.activeCharacter = { id: this.configuration.promptOrder.dummyId }; + } else if ('character' === this.configuration.promptOrder.strategy) { + console.log('FOO') + this.activeCharacter = { id: event.detail.id, ...event.detail.character }; + const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); - // ToDo: These should be passed as parameter or attached to the manager as a set of default options. - // Set default prompts and order for character. - if (0 === promptOrder.length) this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder); + // ToDo: These should be passed as parameter or attached to the manager as a set of default options. + // Set default prompts and order for character. + if (0 === promptOrder.length) this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder); + } else { + throw new Error('Unsupported prompt order mode.'); + } } /** @@ -802,7 +844,13 @@ PromptManagerModule.prototype.handleCharacterSelected = function (event) { * @param event */ PromptManagerModule.prototype.handleCharacterUpdated = function (event) { - this.activeCharacter = {id: event.detail.id, ...event.detail.character}; + if ('global' === this.configuration.promptOrder.strategy) { + this.activeCharacter = { id: this.configuration.promptOrder.dummyId }; + } else if ('character' === this.configuration.promptOrder.strategy) { + this.activeCharacter = { id: event.detail.id, ...event.detail.character }; + } else { + throw new Error('Prompt order strategy not supported.') + } } /** @@ -811,11 +859,17 @@ PromptManagerModule.prototype.handleCharacterUpdated = function (event) { * @param event */ PromptManagerModule.prototype.handleGroupSelected = function (event) { - const characterDummy = {id: event.detail.id, group: event.detail.group}; - this.activeCharacter = characterDummy; - const promptOrder = this.getPromptOrderForCharacter(characterDummy); + if ('global' === this.configuration.promptOrder.strategy) { + this.activeCharacter = { id: this.configuration.promptOrder.dummyId }; + } else if ('character' === this.configuration.promptOrder.strategy) { + const characterDummy = { id: event.detail.id, group: event.detail.group }; + this.activeCharacter = characterDummy; + const promptOrder = this.getPromptOrderForCharacter(characterDummy); - if (0 === promptOrder.length) this.addPromptOrderForCharacter(characterDummy, promptManagerDefaultPromptOrder) + if (0 === promptOrder.length) this.addPromptOrderForCharacter(characterDummy, promptManagerDefaultPromptOrder) + } else { + throw new Error('Prompt order strategy not supported.') + } } /** @@ -823,7 +877,7 @@ PromptManagerModule.prototype.handleGroupSelected = function (event) { * * @returns {string[]} */ -PromptManagerModule.prototype.getActiveGroupCharacters = function() { +PromptManagerModule.prototype.getActiveGroupCharacters = function () { // ToDo: Ideally, this should return the actual characters. return (this.activeCharacter?.group?.members || []).map(member => member && member.substring(0, member.lastIndexOf('.'))); } @@ -937,7 +991,7 @@ PromptManagerModule.prototype.preparePrompt = function (prompt, original = null) * and handle input events to update the prompt content. * */ -PromptManagerModule.prototype.createQuickEdit = function(identifier, title) { +PromptManagerModule.prototype.createQuickEdit = function (identifier, title) { const prompt = this.getPromptById(identifier); const textareaIdentifier = `${identifier}_prompt_quick_edit_textarea`; const html = `
@@ -961,7 +1015,7 @@ PromptManagerModule.prototype.createQuickEdit = function(identifier, title) { } -PromptManagerModule.prototype.updateQuickEdit = function(identifier, prompt) { +PromptManagerModule.prototype.updateQuickEdit = function (identifier, prompt) { const textarea = document.getElementById(`${identifier}_prompt_quick_edit_textarea`); textarea.value = prompt.content; } @@ -973,13 +1027,13 @@ PromptManagerModule.prototype.updateQuickEdit = function(identifier, prompt) { * @param name * @returns {boolean} */ -PromptManagerModule.prototype.isValidName = function(name) { +PromptManagerModule.prototype.isValidName = function (name) { const regex = /^[a-zA-Z0-9_]{1,64}$/; return regex.test(name); } -PromptManagerModule.prototype.sanitizeName = function(name) { +PromptManagerModule.prototype.sanitizeName = function (name) { return name.replace(/[^a-zA-Z0-9_]/g, '_').substring(0, 64); } @@ -1066,7 +1120,7 @@ PromptManagerModule.prototype.clearEditForm = function () { roleField.disabled = false; } -PromptManagerModule.prototype.clearInspectForm = function() { +PromptManagerModule.prototype.clearInspectForm = function () { const inspectArea = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_inspect'); inspectArea.style.display = 'none'; const messageList = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_inspect_list'); @@ -1105,12 +1159,11 @@ PromptManagerModule.prototype.setMessages = function (messages) { * * @param {ChatCompletion} chatCompletion */ -PromptManagerModule.prototype.setChatCompletion = function(chatCompletion) { +PromptManagerModule.prototype.setChatCompletion = function (chatCompletion) { const messages = chatCompletion.getMessages(); this.setMessages(messages); this.populateTokenCounts(messages); - this.populateLegacyTokenCounts(messages); } /** @@ -1118,7 +1171,7 @@ PromptManagerModule.prototype.setChatCompletion = function(chatCompletion) { * * @param {MessageCollection} messages */ -PromptManagerModule.prototype.populateTokenCounts = function(messages) { +PromptManagerModule.prototype.populateTokenCounts = function (messages) { this.tokenHandler.resetCounts(); const counts = this.tokenHandler.getCounts(); messages.getCollection().forEach(message => { @@ -1137,10 +1190,10 @@ PromptManagerModule.prototype.populateTokenCounts = function(messages) { * * @param {MessageCollection} messages */ -PromptManagerModule.prototype.populateLegacyTokenCounts = function(messages) { +PromptManagerModule.prototype.populateLegacyTokenCounts = function (messages) { // Update general token counts const chatHistory = messages.getItemByIdentifier('chatHistory'); - const startChat = chatHistory?.getCollection()[0].getTokens() || 0; + const startChat = chatHistory?.getCollection()[0]?.getTokens() || 0; const continueNudge = chatHistory?.getCollection().find(message => message.identifier === 'continueNudge')?.getTokens() || 0; this.tokenHandler.counts = { @@ -1201,8 +1254,8 @@ PromptManagerModule.prototype.renderPromptManager = function () { - - + +
@@ -1225,10 +1278,14 @@ PromptManagerModule.prototype.renderPromptManager = function () { Export all
- + ${'global' === this.configuration.promptOrder.strategy + ? '' + : `` }
`; @@ -1238,7 +1295,7 @@ PromptManagerModule.prototype.renderPromptManager = function () { let exportPopper = Popper.createPopper( document.getElementById('prompt-manager-export'), document.getElementById('prompt-manager-export-format-popup'), - {placement: 'bottom'} + { placement: 'bottom' } ); const showExportSelection = () => { @@ -1254,17 +1311,49 @@ PromptManagerModule.prototype.renderPromptManager = function () { footerDiv.querySelector('#prompt-manager-import').addEventListener('click', this.handleImport); footerDiv.querySelector('#prompt-manager-export').addEventListener('click', showExportSelection); rangeBlockDiv.querySelector('.export-promptmanager-prompts-full').addEventListener('click', this.handleFullExport); - rangeBlockDiv.querySelector('.export-promptmanager-prompts-character').addEventListener('click', this.handleCharacterExport); + rangeBlockDiv.querySelector('.export-promptmanager-prompts-character')?.addEventListener('click', this.handleCharacterExport); const quickEditContainer = document.getElementById('quick-edit-container'); + const heights = this.saveTextAreaHeights(quickEditContainer); quickEditContainer.innerHTML = ''; this.createQuickEdit('jailbreak', 'Jailbreak'); this.createQuickEdit('nsfw', 'NSFW'); this.createQuickEdit('main', 'Main'); + + this.restoreTextAreaHeights(quickEditContainer, heights); } }; +/** + * Restores the height of each textarea in the container + * @param container The container to search for textareas + * @param heights An object with textarea ids as keys and heights as values + */ +PromptManagerModule.prototype.restoreTextAreaHeights = function(container, heights) { + if (Object.keys(heights).length === 0) return; + + $(container).find('textarea').each(function () { + const height = heights[this.id]; + if (height > 0) $(this).height(height); + }); +} + +/** + * Saves the current height of each textarea in the container + * @param container The container to search for textareas + * @returns {{}} An object with textarea ids as keys and heights as values + */ +PromptManagerModule.prototype.saveTextAreaHeights = function(container) { + const heights = {}; + + $(container).find('textarea').each(function () { + heights[this.id] = $(this).height(); + }); + + return heights; +} + /** * Empties, then re-assembles the prompt list */ @@ -1274,7 +1363,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () { const promptManagerList = this.listElement; promptManagerList.innerHTML = ''; - const {prefix} = this.configuration; + const { prefix } = this.configuration; let listItemHtml = `
  • @@ -1301,7 +1390,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () { let warningTitle = ''; const tokenBudget = this.serviceSettings.openai_max_context - this.serviceSettings.openai_max_tokens; - if ( this.tokenUsage > tokenBudget * 0.8 && + if (this.tokenUsage > tokenBudget * 0.8 && 'chatHistory' === prompt.identifier) { const warningThreshold = this.configuration.warningTokenThreshold; const dangerThreshold = this.configuration.dangerTokenThreshold; @@ -1350,7 +1439,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () { ${prompt.marker ? '' : ''} ${!prompt.marker && prompt.system_prompt ? '' : ''} ${!prompt.marker && !prompt.system_prompt ? '' : ''} - ${this.isPromptInspectionAllowed(prompt) ? `${prompt.name}` : prompt.name } + ${this.isPromptInspectionAllowed(prompt) ? `${prompt.name}` : prompt.name} @@ -1400,7 +1489,7 @@ PromptManagerModule.prototype.export = function (data, type, name = 'export') { }; const serializedObject = JSON.stringify(promptExport); - const blob = new Blob([serializedObject], {type: "application/json"}); + const blob = new Blob([serializedObject], { type: "application/json" }); const url = URL.createObjectURL(blob); const downloadLink = document.createElement('a'); downloadLink.href = url; @@ -1451,10 +1540,19 @@ PromptManagerModule.prototype.import = function (importData) { this.setPrompts(prompts); this.log('Prompt import succeeded'); - if ('character' === importData.type) { - const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); + let promptOrder = []; + if ('global' === this.configuration.promptOrder.strategy) { + const promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId }); Object.assign(promptOrder, importData.data.prompt_order); - this.log(`Prompt order import for character ${this.activeCharacter.name} completed`); + this.log(`Prompt order import succeeded`); + } else if ('character' === this.configuration.promptOrder.strategy) { + if ('character' === importData.type) { + const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); + Object.assign(promptOrder, importData.data.prompt_order); + this.log(`Prompt order import for character ${this.activeCharacter.name} succeeded`); + } + } else { + throw new Error('Prompt order strategy not supported.') } toastr.success('Prompt import complete.'); @@ -1468,7 +1566,7 @@ PromptManagerModule.prototype.import = function (importData) { * @param object * @returns {boolean} */ -PromptManagerModule.prototype.validateObject = function(controlObj, object) { +PromptManagerModule.prototype.validateObject = function (controlObj, object) { for (let key in controlObj) { if (!object.hasOwnProperty(key)) { if (controlObj[key] === null) continue; @@ -1491,7 +1589,7 @@ PromptManagerModule.prototype.validateObject = function(controlObj, object) { * * @returns {`${string}_${string}_${string}`} */ -PromptManagerModule.prototype.getFormattedDate = function() { +PromptManagerModule.prototype.getFormattedDate = function () { const date = new Date(); let month = String(date.getMonth() + 1); let day = String(date.getDate()); @@ -1511,11 +1609,11 @@ PromptManagerModule.prototype.getFormattedDate = function() { */ PromptManagerModule.prototype.makeDraggable = function () { $(`#${this.configuration.prefix}prompt_manager_list`).sortable({ - delay: getSortableDelay(), + delay: this.configuration.sortableDelay, items: `.${this.configuration.prefix}prompt_manager_prompt_draggable`, - update: ( event, ui ) => { + update: (event, ui) => { const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); - const promptListElement = $(`#${this.configuration.prefix}prompt_manager_list`).sortable('toArray', {attribute: 'data-pm-identifier'}); + const promptListElement = $(`#${this.configuration.prefix}prompt_manager_list`).sortable('toArray', { attribute: 'data-pm-identifier' }); const idToObjectMap = new Map(promptOrder.map(prompt => [prompt.identifier, prompt])); const updatedPromptOrder = promptListElement.map(identifier => idToObjectMap.get(identifier)); @@ -1525,7 +1623,8 @@ PromptManagerModule.prototype.makeDraggable = function () { this.log(`Prompt order updated for ${this.activeCharacter.name}.`); this.saveServiceSettings(); - }}); + } + }); }; /** @@ -1536,7 +1635,7 @@ PromptManagerModule.prototype.showPopup = function (area = 'edit') { const areaElement = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_' + area); areaElement.style.display = 'block'; - $('#'+this.configuration.prefix +'prompt_manager_popup').first() + $('#' + this.configuration.prefix + 'prompt_manager_popup').first() .slideDown(200, "swing") .addClass('openDrawer'); } @@ -1546,7 +1645,7 @@ PromptManagerModule.prototype.showPopup = function (area = 'edit') { * @returns {void} */ PromptManagerModule.prototype.hidePopup = function () { - $('#'+this.configuration.prefix +'prompt_manager_popup').first() + $('#' + this.configuration.prefix + 'prompt_manager_popup').first() .slideUp(200, "swing") .removeClass('openDrawer'); } diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js index 87abe5569..3a2d0a395 100644 --- a/public/scripts/RossAscends-mods.js +++ b/public/scripts/RossAscends-mods.js @@ -33,12 +33,12 @@ import { } from "./power-user.js"; import { LoadLocal, SaveLocal, CheckLocal, LoadLocalBool } from "./f-localStorage.js"; -import { selected_group, is_group_generating, getGroupAvatar, groups } from "./group-chats.js"; +import { selected_group, is_group_generating, getGroupAvatar, groups, openGroupById } from "./group-chats.js"; import { SECRET_KEYS, secret_state, } from "./secrets.js"; -import { sortByCssOrder, debounce, delay } from "./utils.js"; +import { debounce, delay } from "./utils.js"; import { chat_completion_sources, oai_settings } from "./openai.js"; var NavToggle = document.getElementById("nav-toggle"); @@ -346,7 +346,7 @@ export function RA_CountCharTokens() { * The character or group is selected (clicked) if it is found. */ async function RA_autoloadchat() { - if (document.getElementById('CharID0') !== null) { + if (document.querySelector('#rm_print_characters_block .character_select') !== null) { // active character is the name, we should look it up in the character list and get the id let active_character_id = Object.keys(characters).find(key => characters[key].avatar === active_character); @@ -354,10 +354,8 @@ async function RA_autoloadchat() { selectCharacterById(String(active_character_id)); } - let groupToAutoLoad = document.querySelector(`.group_select[grid="${active_group}"]`); - - if (groupToAutoLoad != null) { - $(groupToAutoLoad).click(); + if (active_group != null) { + openGroupById(String(active_group)); } // if the character list hadn't been loaded yet, try again. @@ -395,17 +393,18 @@ export async function favsToHotswap() { slot.attr('grid', isGroup ? grid : ''); slot.attr('chid', isCharacter ? chid : ''); slot.data('id', isGroup ? grid : chid); - slot.attr('title', ''); if (isGroup) { const group = groups.find(x => x.id === grid); const avatar = getGroupAvatar(group); $(slot).find('img').replaceWith(avatar); + $(slot).attr('title', group.name); } if (isCharacter) { const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar); $(slot).find('img').attr('src', avatarUrl); + $(slot).attr('title', entity.item.avatar); } $(slot).css('cursor', 'pointer'); @@ -478,6 +477,7 @@ function RA_autoconnect(PrevApi) { || (secret_state[SECRET_KEYS.SCALE] && oai_settings.chat_completion_source == chat_completion_sources.SCALE) || (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) || (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) + || (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21) ) { $("#api_button_openai").click(); } @@ -932,14 +932,16 @@ $("document").ready(function () { // when a char is selected from the list, save their name as the auto-load character for next page load $(document).on("click", ".character_select", function () { - setActiveCharacter($(this).find('.avatar').attr('title')); + const characterId = $(this).find('.avatar').attr('title') || $(this).attr('title'); + setActiveCharacter(characterId); setActiveGroup(null); saveSettingsDebounced(); }); $(document).on("click", ".group_select", function () { + const groupId = $(this).data('id') || $(this).attr('grid'); setActiveCharacter(null); - setActiveGroup($(this).data('id')); + setActiveGroup(groupId); saveSettingsDebounced(); }); diff --git a/public/scripts/bookmarks.js b/public/scripts/bookmarks.js index ba77eda84..294f0266e 100644 --- a/public/scripts/bookmarks.js +++ b/public/scripts/bookmarks.js @@ -1,7 +1,6 @@ import { characters, saveChat, - sendSystemMessage, system_messages, system_message_types, this_chid, @@ -19,6 +18,7 @@ import { getGroupPastChats, group_activation_strategy, groups, + openGroupById, openGroupChat, saveGroupBookmarkChat, selected_group, @@ -301,13 +301,12 @@ async function convertSoloToGroupChat() { } // Click on the freshly selected group to open it - $(`.group_select[grid="${group.id}"]`).click(); + await openGroupById(group.id); - await delay(1); toastr.success('The chat has been successfully converted!'); } -$(document).ready(function () { +jQuery(function () { $('#option_new_bookmark').on('click', saveBookmarkMenu); $('#option_back_to_main').on('click', backToMainChat); $('#option_convert_to_group').on('click', convertSoloToGroupChat); diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index 52715ea99..e9ceca7c4 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -499,7 +499,7 @@ async function moduleWorker() { const context = getContext(); // non-characters not supported - if (!context.groupId && context.characterId === undefined) { + if (!context.groupId && (context.characterId === undefined || context.characterId === 'invalid-safety-id')) { removeExpression(); return; } diff --git a/public/scripts/extensions/memory/index.js b/public/scripts/extensions/memory/index.js index e33994bc1..65dc2098c 100644 --- a/public/scripts/extensions/memory/index.js +++ b/public/scripts/extensions/memory/index.js @@ -380,8 +380,7 @@ async function summarizeChatMain(context, force) { } console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary); - const prompt = substituteParams(extension_settings.memory.prompt) - .replace(/{{words}}/gi, extension_settings.memory.promptWords); + const prompt = extension_settings.memory.prompt?.replace(/{{words}}/gi, extension_settings.memory.promptWords); if (!prompt) { console.debug('Summarization prompt is empty. Skipping summarization.'); diff --git a/public/scripts/extensions/objective/index.js b/public/scripts/extensions/objective/index.js index 449a2c98c..211d8243c 100644 --- a/public/scripts/extensions/objective/index.js +++ b/public/scripts/extensions/objective/index.js @@ -72,20 +72,22 @@ function getTaskByIdRecurse(taskId, task) { return null; } -function substituteParamsPrompts(content) { +function substituteParamsPrompts(content, substituteGlobal) { content = content.replace(/{{objective}}/gi, currentObjective.description) content = content.replace(/{{task}}/gi, currentTask.description) if (currentTask.parent){ content = content.replace(/{{parent}}/gi, currentTask.parent.description) } - content = substituteParams(content) + if (substituteGlobal) { + content = substituteParams(content) + } return content } // Call Quiet Generate to create task list using character context, then convert to tasks. Should not be called much. async function generateTasks() { - const prompt = substituteParamsPrompts(objectivePrompts.createTask); + const prompt = substituteParamsPrompts(objectivePrompts.createTask, false); console.log(`Generating tasks for objective with prompt`) toastr.info('Generating tasks for objective', 'Please wait...'); const taskResponse = await generateQuietPrompt(prompt) @@ -128,7 +130,7 @@ async function checkTaskCompleted() { checkCounter = $('#objective-check-frequency').val() toastr.info("Checking for task completion.") - const prompt = substituteParamsPrompts(objectivePrompts.checkTaskCompleted); + const prompt = substituteParamsPrompts(objectivePrompts.checkTaskCompleted, false); const taskResponse = (await generateQuietPrompt(prompt)).toLowerCase() // Check response if task complete @@ -178,7 +180,7 @@ function setCurrentTask(taskId = null) { // Don't just check for a current task, check if it has data const description = currentTask.description || null; if (description) { - const extensionPromptText = substituteParamsPrompts(objectivePrompts.currentTask); + const extensionPromptText = substituteParamsPrompts(objectivePrompts.currentTask, true); // Remove highlights $('.objective-task').css({'border-color':'','border-width':''}) diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index 41accbb12..646fa0495 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -1,5 +1,4 @@ import { - substituteParams, saveSettingsDebounced, systemUserName, hideSwipeButtons, @@ -14,7 +13,8 @@ import { } from "../../../script.js"; import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js"; import { selected_group } from "../../group-chats.js"; -import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename } from "../../utils.js"; +import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename, saveBase64AsFile } from "../../utils.js"; +import { humanizedDateTime } from "../../RossAscends-mods.js"; export { MODULE_NAME }; // Wraps a string into monospace font-face span @@ -512,7 +512,7 @@ function getQuietPrompt(mode, trigger) { return trigger; } - return substituteParams(stringFormat(extension_settings.sd.prompts[mode], trigger)); + return stringFormat(extension_settings.sd.prompts[mode], trigger); } function processReply(str) { @@ -537,6 +537,7 @@ function processReply(str) { return str; } + function getRawLastMessage() { const context = getContext(); const lastMessage = context.chat.slice(-1)[0].mes, @@ -565,6 +566,10 @@ async function generatePicture(_, trigger, message, callback) { const quiet_prompt = getQuietPrompt(generationType, trigger); const context = getContext(); + // if context.characterId is not null, then we get context.characters[context.characterId].avatar, else we get groupId and context.groups[groupId].id + // sadly, groups is not an array, but is a dict with keys being index numbers, so we have to filter it + const characterName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]].id.toString(); + const prevSDHeight = extension_settings.sd.height; const prevSDWidth = extension_settings.sd.width; const aspectRatio = extension_settings.sd.width / extension_settings.sd.height; @@ -580,8 +585,10 @@ async function generatePicture(_, trigger, message, callback) { // Round to nearest multiple of 64 extension_settings.sd.width = Math.round(extension_settings.sd.height * 1.8 / 64) * 64; const callbackOriginal = callback; - callback = function (prompt, base64Image) { - const imgUrl = `url(${base64Image})`; + callback = async function (prompt, base64Image) { + const imagePath = base64Image; + const imgUrl = `url('${encodeURIComponent(base64Image)}')`; + if ('forceSetBackground' in window) { forceSetBackground(imgUrl); } else { @@ -590,9 +597,9 @@ async function generatePicture(_, trigger, message, callback) { } if (typeof callbackOriginal === 'function') { - callbackOriginal(prompt, base64Image); + callbackOriginal(prompt, imagePath); } else { - sendMessage(prompt, base64Image); + sendMessage(prompt, imagePath); } } } @@ -604,7 +611,7 @@ async function generatePicture(_, trigger, message, callback) { context.deactivateSendButtons(); hideSwipeButtons(); - await sendGenerationRequest(generationType, prompt, callback); + await sendGenerationRequest(generationType, prompt, characterName, callback); } catch (err) { console.trace(err); throw new Error('SD prompt text generation failed.') @@ -644,19 +651,31 @@ async function generatePrompt(quiet_prompt) { return processReply(reply); } -async function sendGenerationRequest(generationType, prompt, callback) { +async function sendGenerationRequest(generationType, prompt, characterName = null, callback) { const prefix = generationType !== generationMode.BACKGROUND ? combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()) : extension_settings.sd.prompt_prefix; if (extension_settings.sd.horde) { - await generateHordeImage(prompt, prefix, callback); + await generateHordeImage(prompt, prefix, characterName, callback); } else { - await generateExtrasImage(prompt, prefix, callback); + await generateExtrasImage(prompt, prefix, characterName, callback); } } -async function generateExtrasImage(prompt, prefix, callback) { +/** + * Generates an "extras" image using a provided prompt and other settings, + * then saves the generated image and either invokes a callback or sends a message with the image. + * + * @param {string} prompt - The main instruction used to guide the image generation. + * @param {string} prefix - Additional context or prefix to guide the image generation. + * @param {string} characterName - The name used to determine the sub-directory for saving. + * @param {function} [callback] - Optional callback function invoked with the prompt and saved image. + * If not provided, `sendMessage` is called instead. + * + * @returns {Promise} - A promise that resolves when the image generation and processing are complete. + */ +async function generateExtrasImage(prompt, prefix, characterName, callback) { console.debug(extension_settings.sd); const url = new URL(getApiUrl()); url.pathname = '/api/image'; @@ -680,14 +699,28 @@ async function generateExtrasImage(prompt, prefix, callback) { if (result.ok) { const data = await result.json(); - const base64Image = `data:image/jpeg;base64,${data.image}`; + //filename should be character name + human readable timestamp + generation mode + const filename = `${characterName}_${humanizedDateTime()}`; + const base64Image = await saveBase64AsFile(data.image, characterName, filename, "jpg"); callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image); } else { callPopup('Image generation has failed. Please try again.', 'text'); } } -async function generateHordeImage(prompt, prefix, callback) { +/** + * Generates a "horde" image using the provided prompt and configuration settings, + * then saves the generated image and either invokes a callback or sends a message with the image. + * + * @param {string} prompt - The main instruction used to guide the image generation. + * @param {string} prefix - Additional context or prefix to guide the image generation. + * @param {string} characterName - The name used to determine the sub-directory for saving. + * @param {function} [callback] - Optional callback function invoked with the prompt and saved image. + * If not provided, `sendMessage` is called instead. + * + * @returns {Promise} - A promise that resolves when the image generation and processing are complete. + */ +async function generateHordeImage(prompt, prefix, characterName, callback) { const result = await fetch('/horde_generateimage', { method: 'POST', headers: getRequestHeaders(), @@ -709,7 +742,8 @@ async function generateHordeImage(prompt, prefix, callback) { if (result.ok) { const data = await result.text(); - const base64Image = `data:image/webp;base64,${data}`; + const filename = `${characterName}_${humanizedDateTime()}`; + const base64Image = await saveBase64AsFile(data, characterName, filename, "webp"); callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image); } else { toastr.error('Image generation has failed. Please try again.'); @@ -827,7 +861,7 @@ async function sdMessageButton(e) { const message_id = $mes.attr('mesid'); const message = context.chat[message_id]; const characterName = message?.name || context.name2; - const messageText = substituteParams(message?.mes); + const messageText = message?.mes; const hasSavedImage = message?.extra?.image && message?.extra?.title; if ($icon.hasClass(busyClass)) { @@ -842,7 +876,7 @@ async function sdMessageButton(e) { message.extra.title = prompt; console.log('Regenerating an image, using existing prompt:', prompt); - await sendGenerationRequest(generationMode.FREE, prompt, saveGeneratedImage); + await sendGenerationRequest(generationMode.FREE, prompt, characterName, saveGeneratedImage); } else { console.log("doing /sd raw last"); diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 3859efb4c..3884b85a2 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -6,6 +6,7 @@ import { isDataURL, createThumbnail, extractAllWords, + saveBase64AsFile } from './utils.js'; import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap } from "./RossAscends-mods.js"; import { loadMovingUIState, sortEntitiesList } from './power-user.js'; @@ -63,8 +64,8 @@ import { getCropPopup, system_avatar, } from "../script.js"; -import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js'; -import { FilterHelper } from './filters.js'; +import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map, printTagFilters } from './tags.js'; +import { FILTER_TYPES, FilterHelper } from './filters.js'; export { selected_group, @@ -80,6 +81,7 @@ export { regenerateGroup, resetSelectedGroup, select_group_chats, + getGroupChatNames, } let is_group_generating = false; // Group generation flag @@ -175,6 +177,7 @@ export async function getGroupChat(groupId) { addOneMessage(mes); } } + await saveGroupChat(groupId, false); } if (group) { @@ -182,7 +185,6 @@ export async function getGroupChat(groupId) { updateChatMetadata(metadata, true); } - await saveGroupChat(groupId, true); eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId()); } @@ -308,6 +310,9 @@ async function getGroups() { // Convert groups to new format for (const group of groups) { + if (typeof group.id === 'number') { + group.id = String(group.id); + } if (group.disabled_members == undefined) { group.disabled_members = []; } @@ -333,25 +338,25 @@ async function getGroups() { } export function getGroupBlock(group) { - const template = $("#group_list_template .group_select").clone(); - template.data("id", group.id); - template.attr("grid", group.id); - template.find(".ch_name").html(group.name); - template.find('.group_fav_icon').css("display", 'none'); - template.addClass(group.fav ? 'is_fav' : ''); - template.find(".ch_fav").val(group.fav); + const template = $("#group_list_template .group_select").clone(); + template.data("id", group.id); + template.attr("grid", group.id); + template.find(".ch_name").text(group.name); + template.find('.group_fav_icon').css("display", 'none'); + template.addClass(group.fav ? 'is_fav' : ''); + template.find(".ch_fav").val(group.fav); - // Display inline tags - const tags = getTagsList(group.id); - const tagsElement = template.find('.tags'); - tags.forEach(tag => appendTagToList(tagsElement, tag, {})); + // Display inline tags + const tags = getTagsList(group.id); + const tagsElement = template.find('.tags'); + tags.forEach(tag => appendTagToList(tagsElement, tag, {})); - const avatar = getGroupAvatar(group); - if (avatar) { - $(template).find(".avatar").replaceWith(avatar); - } + const avatar = getGroupAvatar(group); + if (avatar) { + $(template).find(".avatar").replaceWith(avatar); + } - return template; + return template; } function updateGroupAvatar(group) { @@ -359,17 +364,26 @@ function updateGroupAvatar(group) { $(".group_select").each(function () { if ($(this).data("id") == group.id) { - $(this).find(".avatar").replaceWith(getGroupAvatar(group)); + $(this).find(".avatar").replaceWith(getGroupAvatar(group)); } }); } +// check if isDataURLor if it's a valid local file url +function isValidImageUrl(url) { + // check if empty dict + if (Object.keys(url).length === 0) { + return false; + } + return isDataURL(url) || (url && url.startsWith("user")); +} + function getGroupAvatar(group) { if (!group) { return $(`
    `); } - - if (isDataURL(group.avatar_url)) { + // if isDataURL or if it's a valid local file url + if (isValidImageUrl(group.avatar_url)) { return $(`
    `); } @@ -405,6 +419,19 @@ function getGroupAvatar(group) { return groupAvatar; } +function getGroupChatNames(groupId) { + const group = groups.find(x => x.id === groupId); + + if (!group) { + return []; + } + + const names = []; + for (const chatId of group.chats) { + names.push(chatId); + } + return names; +} async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { if (online_status === "no_connection") { @@ -802,10 +829,7 @@ async function deleteGroup(id) { printMessages(); await getCharacters(); - $("#rm_info_avatar").html(""); - $("#rm_info_block").transition({ opacity: 0, duration: 0 }); select_rm_info("group_delete", id); - $("#rm_info_block").transition({ opacity: 1.0, duration: 2000 }); $("#rm_button_selected_ch").children("h2").text(''); setRightTabSelectedClass(); @@ -957,6 +981,7 @@ function getGroupCharacters({ doFilter, onlyMembers } = {}) { } function printGroupCandidates() { + const storageKey = 'GroupCandidates_PerPage'; $("#rm_group_add_members_pagination").pagination({ dataSource: getGroupCharacters({ doFilter: true, onlyMembers: false }), pageSize: 5, @@ -967,6 +992,12 @@ function printGroupCandidates() { prevText: '<', nextText: '>', showNavigator: true, + showSizeChanger: true, + pageSize: Number(localStorage.getItem(storageKey)) || 5, + sizeChangerOptions: [5, 10, 25, 50, 100, 200], + afterSizeSelectorChange: function (e) { + localStorage.setItem(storageKey, e.target.value); + }, callback: function (data) { $("#rm_group_add_members").empty(); for (const i of data) { @@ -977,6 +1008,7 @@ function printGroupCandidates() { } function printGroupMembers() { + const storageKey = 'GroupMembers_PerPage'; $("#rm_group_members_pagination").pagination({ dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }), pageSize: 5, @@ -987,6 +1019,12 @@ function printGroupMembers() { prevText: '<', nextText: '>', showNavigator: true, + showSizeChanger: true, + pageSize: Number(localStorage.getItem(storageKey)) || 5, + sizeChangerOptions: [5, 10, 25, 50, 100, 200], + afterSizeSelectorChange: function (e) { + localStorage.setItem(storageKey, e.target.value); + }, callback: function (data) { $("#rm_group_members").empty(); for (const i of data) { @@ -1065,8 +1103,7 @@ function select_group_chats(groupId, skipAnimation) { setMenuType(!!group ? 'group_edit' : 'group_create'); $("#group_avatar_preview").empty().append(getGroupAvatar(group)); - $("#rm_group_restore_avatar").toggle(!!group && isDataURL(group.avatar_url)); - $("#rm_group_chat_name").val(groupName); + $("#rm_group_restore_avatar").toggle(!!group && isValidImageUrl(group.avatar_url)); $("#rm_group_filter").val("").trigger("input"); $(`input[name="rm_group_activation_strategy"][value="${replyStrategy}"]`).prop('checked', true); @@ -1108,9 +1145,18 @@ function select_group_chats(groupId, skipAnimation) { $("#rm_group_automode_label").hide(); } - eventSource.emit('groupSelected', {detail: {id: openGroupId, group: group}}); + eventSource.emit('groupSelected', { detail: { id: openGroupId, group: group } }); } +/** + * Handles the upload and processing of a group avatar. + * The selected image is read, cropped using a popup, processed into a thumbnail, + * and then uploaded to the server. + * + * @param {Event} event - The event triggered by selecting a file input, containing the image file to upload. + * + * @returns {Promise} - A promise that resolves when the processing and upload is complete. + */ async function uploadGroupAvatar(event) { const file = event.target.files[0]; @@ -1133,16 +1179,22 @@ async function uploadGroupAvatar(event) { return; } - const thumbnail = await createThumbnail(croppedImage, 96, 144); - + let thumbnail = await createThumbnail(croppedImage, 96, 144); + //remove data:image/whatever;base64 + thumbnail = thumbnail.replace(/^data:image\/[a-z]+;base64,/, ""); + let _thisGroup = groups.find((x) => x.id == openGroupId); + // filename should be group id + human readable timestamp + const filename = `${_thisGroup.id}_${humanizedDateTime()}`; + let thumbnailUrl = await saveBase64AsFile(thumbnail, openGroupId.toString(), filename, 'jpg'); if (!openGroupId) { - $('#group_avatar_preview img').attr('src', thumbnail); + $('#group_avatar_preview img').attr('src', thumbnailUrl); $('#rm_group_restore_avatar').show(); return; } - let _thisGroup = groups.find((x) => x.id == openGroupId); - _thisGroup.avatar_url = thumbnail; + + + _thisGroup.avatar_url = thumbnailUrl; $("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup)); $("#rm_group_restore_avatar").show(); await editGroup(openGroupId, true, true); @@ -1223,8 +1275,11 @@ function updateFavButtonState(state) { $("#group_favorite_button").toggleClass('fav_off', !fav_grp_checked); } -async function selectGroup() { - const groupId = $(this).data("id"); +export async function openGroupById(groupId) { + if (!groups.find(x => x.id === groupId)) { + console.log('Group not found', groupId); + return; + } if (!is_send_press && !is_group_generating) { if (selected_group !== groupId) { @@ -1265,16 +1320,8 @@ function openCharacterDefinition(characterSelect) { } function filterGroupMembers() { - const searchValue = $(this).val().trim().toLowerCase(); - - if (!searchValue) { - $("#rm_group_add_members .group_member").removeClass('hiddenBySearch'); - } else { - $("#rm_group_add_members .group_member").each(function () { - const isValidSearch = $(this).find(".ch_name").text().toLowerCase().includes(searchValue); - $(this).toggleClass('hiddenBySearch', !isValidSearch); - }); - } + const searchValue = $(this).val().toLowerCase(); + groupCandidatesFilter.setFilterData(FILTER_TYPES.SEARCH, searchValue); } async function createGroup() { @@ -1299,7 +1346,7 @@ async function createGroup() { body: JSON.stringify({ name: name, members: members, - avatar_url: isDataURL(avatar_url) ? avatar_url : default_avatar, + avatar_url: isValidImageUrl(avatar_url) ? avatar_url : default_avatar, allow_self_responses: allow_self_responses, activation_strategy: activation_strategy, disabled_members: [], @@ -1545,7 +1592,10 @@ function doCurMemberListPopout() { } jQuery(() => { - $(document).on("click", ".group_select", selectGroup); + $(document).on("click", ".group_select", function () { + const groupId = $(this).data("id"); + openGroupById(groupId); + }); $("#rm_group_filter").on("input", filterGroupMembers); $("#rm_group_submit").on("click", createGroup); $("#rm_group_scenario").on("click", setScenarioOverride); diff --git a/public/scripts/horde.js b/public/scripts/horde.js index 061b1c640..d7094f649 100644 --- a/public/scripts/horde.js +++ b/public/scripts/horde.js @@ -8,6 +8,8 @@ import { import { SECRET_KEYS, writeSecret } from "./secrets.js"; import { delay } from "./utils.js"; import { deviceInfo } from "./RossAscends-mods.js"; +import { power_user } from "./power-user.js"; +import { autoSelectInstructPreset } from "./instruct-mode.js"; export { horde_settings, @@ -226,19 +228,11 @@ async function showKudos() { jQuery(function () { $("#horde_model").on('mousedown change', async function (e) { - //desktop-only routine for multi-select without CTRL - /*if (deviceInfo.device.type === 'desktop') { - let hordeModelSelectScrollTop = null; - e.preventDefault(); - const option = $(e.target); - const selectElement = $(this)[0]; - hordeModelSelectScrollTop = selectElement.scrollTop; - option.prop('selected', !option.prop('selected')); - await delay(1); - selectElement.scrollTop = hordeModelSelectScrollTop; - }*/ horde_settings.models = $('#horde_model').val(); console.log('Updated Horde models', horde_settings.models); + + // Try select instruct preset + autoSelectInstructPreset(horde_settings.models.join(' ')); }); $("#horde_auto_adjust_response_length").on("input", function () { diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js new file mode 100644 index 000000000..bb99615d4 --- /dev/null +++ b/public/scripts/instruct-mode.js @@ -0,0 +1,277 @@ +"use strict"; + +import { saveSettingsDebounced, substituteParams } from "../script.js"; +import { selected_group } from "./group-chats.js"; +import { power_user } from "./power-user.js"; + +export let instruct_presets = []; + +const controls = [ + { id: "instruct_enabled", property: "enabled", isCheckbox: true }, + { id: "instruct_wrap", property: "wrap", isCheckbox: true }, + { id: "instruct_system_prompt", property: "system_prompt", isCheckbox: false }, + { id: "instruct_system_sequence", property: "system_sequence", isCheckbox: false }, + { id: "instruct_separator_sequence", property: "separator_sequence", isCheckbox: false }, + { id: "instruct_input_sequence", property: "input_sequence", isCheckbox: false }, + { id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false }, + { id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false }, + { id: "instruct_names", property: "names", isCheckbox: true }, + { id: "instruct_macro", property: "macro", isCheckbox: true }, + { id: "instruct_names_force_groups", property: "names_force_groups", isCheckbox: true }, + { id: "instruct_last_output_sequence", property: "last_output_sequence", isCheckbox: false }, + { id: "instruct_activation_regex", property: "activation_regex", isCheckbox: false }, +]; + +/** + * Loads instruct mode settings from the given data object. + * @param {object} data Settings data object. + */ +export function loadInstructMode(data) { + if (data.instruct !== undefined) { + instruct_presets = data.instruct; + } + + if (power_user.instruct.names_force_groups === undefined) { + power_user.instruct.names_force_groups = true; + } + + controls.forEach(control => { + const $element = $(`#${control.id}`); + + if (control.isCheckbox) { + $element.prop('checked', power_user.instruct[control.property]); + } else { + $element.val(power_user.instruct[control.property]); + } + + $element.on('input', function () { + power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val(); + saveSettingsDebounced(); + }); + }); + + instruct_presets.forEach((preset) => { + const name = preset.name; + const option = document.createElement('option'); + option.value = name; + option.innerText = name; + option.selected = name === power_user.instruct.preset; + $('#instruct_presets').append(option); + }); + + highlightDefaultPreset(); +} + +function highlightDefaultPreset() { + $('#instruct_set_default').toggleClass('default', power_user.default_instruct === power_user.instruct.preset); +} + +/** + * Automatically select instruct preset based on model id. + * Otherwise, if default instruct preset is set, selects it. + * @param {string} modelId Model name reported by the API. + * @returns {boolean} True if instruct preset was activated by model id, false otherwise. + */ +export function autoSelectInstructPreset(modelId) { + // If instruct mode is disabled, don't do anything + if (!power_user.instruct.enabled) { + return false; + } + + for (const preset of instruct_presets) { + // If activation regex is set, check if it matches the model id + if (preset.activation_regex) { + try { + const regex = new RegExp(preset.activation_regex, 'i'); + + // Stop on first match so it won't cycle back and forth between presets if multiple regexes match + if (regex.test(modelId)) { + // If preset is not already selected, select it + if (power_user.instruct.preset !== preset.name) { + $('#instruct_presets').val(preset.name).trigger('change'); + toastr.info(`Instruct mode: preset "${preset.name}" auto-selected`); + + return true; + } + } + } catch { + // If regex is invalid, ignore it + console.warn(`Invalid instruct activation regex in preset "${preset.name}"`); + } + } + } + + if (power_user.default_instruct && power_user.instruct.preset !== power_user.default_instruct) { + if (instruct_presets.some(p => p.name === power_user.default_instruct)) { + console.log(`Instruct mode: default preset "${power_user.default_instruct}" selected`); + $('#instruct_presets').val(power_user.default_instruct).trigger('change'); + } + } + + return false; +} + +/** + * Converts instruct mode sequences to an array of stopping strings. + * @returns {string[]} Array of instruct mode stopping strings. + */ +export function getInstructStoppingSequences() { + function addInstructSequence(sequence) { + // Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string + // But it's a problem for Metharme which doesn't use newlines to separate them. + const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s; + // Sequence must be a non-empty string + if (typeof sequence === 'string' && sequence.length > 0) { + // If sequence is just a whitespace or newline - we don't want to make it a stopping string + // User can always add it as a custom stop string if really needed + if (sequence.trim().length > 0) { + const wrappedSequence = wrap(sequence); + // Need to respect "insert macro" setting + const stopString = power_user.instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence; + result.push(stopString); + } + } + } + + const result = []; + + if (power_user.instruct.enabled) { + const input_sequence = power_user.instruct.input_sequence; + const output_sequence = power_user.instruct.output_sequence; + const last_output_sequence = power_user.instruct.last_output_sequence; + + const combined_sequence = `${input_sequence}\n${output_sequence}\n${last_output_sequence}`; + + combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence); + } + + return result; +} + +/** + * Formats instruct mode chat message. + * @param {string} name Character name. + * @param {string} mes Message text. + * @param {boolean} isUser Is the message from the user. + * @param {boolean} isNarrator Is the message from the narrator. + * @param {string} forceAvatar Force avatar string. + * @param {string} name1 User name. + * @param {string} name2 Character name. + * @returns {string} Formatted instruct mode chat message. + */ +export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) { + let includeNames = isNarrator ? false : power_user.instruct.names; + + if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) { + includeNames = true; + } + + let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence; + + if (power_user.instruct.macro) { + sequence = substituteParams(sequence, name1, name2); + } + + const separator = power_user.instruct.wrap ? '\n' : ''; + const separatorSequence = power_user.instruct.separator_sequence && !isUser + ? power_user.instruct.separator_sequence + : separator; + const textArray = includeNames ? [sequence, `${name}: ${mes}` + separatorSequence] : [sequence, mes + separatorSequence]; + const text = textArray.filter(x => x).join(separator); + return text; +} + +/** + * Formats example messages according to instruct mode settings. + * @param {string} mesExamples Example messages string. + * @param {string} name1 User name. + * @param {string} name2 Character name. + * @returns {string} Formatted example messages string. + */ +export function formatInstructModeExamples(mesExamples, name1, name2) { + const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups); + + let inputSequence = power_user.instruct.input_sequence; + let outputSequence = power_user.instruct.output_sequence; + + if (power_user.instruct.macro) { + inputSequence = substituteParams(inputSequence, name1, name2); + outputSequence = substituteParams(outputSequence, name1, name2); + } + + const separator = power_user.instruct.wrap ? '\n' : ''; + const separatorSequence = power_user.instruct.separator_sequence ? power_user.instruct.separator_sequence : separator; + + mesExamples = mesExamples.replace(new RegExp(`\n${name1}: `, "gm"), separatorSequence + inputSequence + separator + (includeNames ? `${name1}: ` : '')); + mesExamples = mesExamples.replace(new RegExp(`\n${name2}: `, "gm"), separator + outputSequence + separator + (includeNames ? `${name2}: ` : '')); + + return mesExamples; +} + +/** + * Formats instruct mode last prompt line. + * @param {string} name Character name. + * @param {boolean} isImpersonate Is generation in impersonation mode. + * @param {string} promptBias Prompt bias string. + * @param {string} name1 User name. + * @param {string} name2 Character name. + */ +export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) { + const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups); + const getOutputSequence = () => power_user.instruct.last_output_sequence || power_user.instruct.output_sequence; + let sequence = isImpersonate ? power_user.instruct.input_sequence : getOutputSequence(); + + if (power_user.instruct.macro) { + sequence = substituteParams(sequence, name1, name2); + } + + const separator = power_user.instruct.wrap ? '\n' : ''; + let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence); + + if (!isImpersonate && promptBias) { + text += (includeNames ? promptBias : (separator + promptBias)); + } + + return text.trimEnd() + (includeNames ? '' : separator); +} + +jQuery(() => { + $('#instruct_set_default').on('click', function () { + if (power_user.instruct.preset === power_user.default_instruct) { + power_user.default_instruct = null; + $(this).removeClass('default'); + toastr.info('Default instruct preset cleared'); + } else { + power_user.default_instruct = power_user.instruct.preset; + $(this).addClass('default'); + toastr.info(`Default instruct preset set to ${power_user.default_instruct}`); + } + + saveSettingsDebounced(); + }); + + $('#instruct_presets').on('change', function () { + const name = $(this).find(':selected').val(); + const preset = instruct_presets.find(x => x.name === name); + + if (!preset) { + return; + } + + power_user.instruct.preset = name; + controls.forEach(control => { + if (preset[control.property] !== undefined) { + power_user.instruct[control.property] = preset[control.property]; + const $element = $(`#${control.id}`); + + if (control.isCheckbox) { + $element.prop('checked', power_user.instruct[control.property]).trigger('input'); + } else { + $element.val(power_user.instruct[control.property]).trigger('input'); + } + } + }); + + highlightDefaultPreset(); + }); +}); diff --git a/public/scripts/nai-settings.js b/public/scripts/nai-settings.js index b18306922..e19c90601 100644 --- a/public/scripts/nai-settings.js +++ b/public/scripts/nai-settings.js @@ -576,7 +576,7 @@ function calculateLogitBias() { } /** - * Transforms instruction into compatible format for Novel AI. + * Transforms instruction into compatible format for Novel AI if Novel AI instruct format not already detected. * 1. Instruction must begin and end with curly braces followed and preceded by a space. * 2. Instruction must not contain square brackets as it serves different purpose in NAI. * @param {string} prompt Original instruction prompt @@ -584,7 +584,10 @@ function calculateLogitBias() { */ export function adjustNovelInstructionPrompt(prompt) { const stripedPrompt = prompt.replace(/[\[\]]/g, '').trim(); - return `{ ${stripedPrompt} }`; + if (!stripedPrompt.includes('{ ')) { + return `{ ${stripedPrompt} }`; + } + return stripedPrompt; } export async function generateNovelWithStreaming(generate_data, signal) { diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 71b83b9f2..a47666c20 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -19,7 +19,6 @@ import { system_message_types, replaceBiasMarkup, is_send_press, - saveSettings, Generate, main_api, eventSource, @@ -45,9 +44,10 @@ import { } from "./secrets.js"; import { + deepClone, delay, download, - getFileText, + getFileText, getSortableDelay, getStringHash, parseJsonFile, stringFormat, @@ -113,9 +113,13 @@ const scale_max = 7900; // Probably more. Save some for the system prompt define const claude_max = 8000; // We have a proper tokenizer, so theoretically could be larger (up to 9k) const palm2_max = 7500; // The real context window is 8192, spare some for padding due to using turbo tokenizer const claude_100k_max = 99000; +let ai21_max = 9200; //can easily fit 9k gpt tokens because j2's tokenizer is efficient af const unlocked_max = 100 * 1024; const oai_max_temp = 2.0; -const claude_max_temp = 1.0; +const claude_max_temp = 1.0; //same as j2 +const j2_max_topk = 10.0; +const j2_max_freq = 5.0; +const j2_max_pres = 5.0; const openrouter_website_model = 'OR_Website'; let biasCache = undefined; @@ -161,13 +165,26 @@ export const chat_completion_sources = { CLAUDE: 'claude', SCALE: 'scale', OPENROUTER: 'openrouter', + AI21: 'ai21', }; +const prefixMap = selected_group ? { + assistant: "", + user: "", + system: "OOC: " +} + : { + assistant: "{{char}}:", + user: "{{user}}:", + system: "" + }; + const default_settings = { preset_settings_openai: 'Default', temp_openai: 0.9, freq_pen_openai: 0.7, pres_pen_openai: 0.7, + count_pen: 0.0, top_p_openai: 1.0, top_k_openai: 0, stream_openai: false, @@ -188,6 +205,7 @@ const default_settings = { wi_format: default_wi_format, openai_model: 'gpt-3.5-turbo', claude_model: 'claude-instant-v1', + ai21_model: 'j2-ultra', windowai_model: '', openrouter_model: openrouter_website_model, jailbreak_system: false, @@ -199,6 +217,8 @@ const default_settings = { show_external_models: false, proxy_password: '', assistant_prefill: '', + use_ai21_tokenizer: false, + exclude_assistant: false, }; const oai_settings = { @@ -206,6 +226,7 @@ const oai_settings = { temp_openai: 1.0, freq_pen_openai: 0, pres_pen_openai: 0, + count_pen: 0.0, top_p_openai: 1.0, top_k_openai: 0, stream_openai: false, @@ -226,6 +247,7 @@ const oai_settings = { wi_format: default_wi_format, openai_model: 'gpt-3.5-turbo', claude_model: 'claude-instant-v1', + ai21_model: 'j2-ultra', windowai_model: '', openrouter_model: openrouter_website_model, jailbreak_system: false, @@ -237,6 +259,8 @@ const oai_settings = { show_external_models: false, proxy_password: '', assistant_prefill: '', + use_ai21_tokenizer: false, + exclude_assistant: false, }; let openai_setting_names; @@ -341,17 +365,22 @@ function setupChatCompletionPromptManager(openAiSettings) { containerIdentifier: 'completion_prompt_manager', listIdentifier: 'completion_prompt_manager_list', toggleDisabled: ['main'], - draggable: true, + sortableDelay: getSortableDelay(), defaultPrompts: { main: default_main_prompt, nsfw: default_nsfw_prompt, jailbreak: default_jailbreak_prompt, enhanceDefinitions: default_enhance_definitions_prompt - } + }, + promptOrder: { + strategy: 'global', + dummyId: 100000 + }, }; promptManager.saveServiceSettings = () => { - return saveSettings(); + saveSettingsDebounced(); + return new Promise((resolve) => eventSource.once(event_types.SETTINGS_UPDATED, resolve)); } promptManager.tryGenerate = () => { @@ -361,7 +390,7 @@ function setupChatCompletionPromptManager(openAiSettings) { promptManager.tokenHandler = tokenHandler; promptManager.init(configuration, openAiSettings); - promptManager.render(); + promptManager.render(false); return promptManager; } @@ -451,11 +480,19 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt = // Chat History chatCompletion.add(new MessageCollection('chatHistory'), prompts.index('chatHistory')); + let names = (selected_group && groups.find(x => x.id === selected_group)?.members.map(member => characters.find(c => c.avatar === member)?.name).filter(Boolean).join(', ')) || ''; // Reserve budget for new chat message const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt; - const newChatMessage = new Message('system', newChat, 'newMainChat'); + const newChatMessage = new Message('system', substituteParams(newChat, null, null, null, names), 'newMainChat'); chatCompletion.reserveBudget(newChatMessage); + // Reserve budget for group nudge + let groupNudgeMessage = null; + if (selected_group) { + const groupNudgeMessage = Message.fromPrompt(prompts.get('groupNudge')); + chatCompletion.reserveBudget(groupNudgeMessage); + } + // Reserve budget for continue nudge let continueMessage = null; if (type === 'continue' && cyclePrompt) { @@ -484,7 +521,8 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt = const chatMessage = Message.fromPrompt(promptManager.preparePrompt(prompt)); if (true === promptManager.serviceSettings.names_in_completion && prompt.name) { - chatMessage.name = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name); + const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name); + chatMessage.setName(messageName); } if (chatCompletion.canAfford(chatMessage)) chatCompletion.insertAtStart(chatMessage, 'chatHistory'); @@ -496,6 +534,12 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt = chatCompletion.freeBudget(newChatMessage); chatCompletion.insertAtStart(newChatMessage, 'chatHistory'); + // Reserve budget for group nudge + if (selected_group && groupNudgeMessage) { + chatCompletion.freeBudget(groupNudgeMessage); + chatCompletion.insertAtEnd(groupNudgeMessage, 'chatHistory'); + } + // Insert and free continue nudge if (type === 'continue' && continueMessage) { chatCompletion.freeBudget(continueMessage); @@ -513,9 +557,11 @@ function populateDialogueExamples(prompts, chatCompletion) { chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples')); if (openai_msgs_example.length) { const newExampleChat = new Message('system', oai_settings.new_example_chat_prompt, 'newChat'); - chatCompletion.reserveBudget(newExampleChat); - [...openai_msgs_example].forEach((dialogue, dialogueIndex) => { + let examplesAdded = 0; + + if (chatCompletion.canAfford(newExampleChat)) chatCompletion.insert(newExampleChat, 'dialogueExamples'); + dialogue.forEach((prompt, promptIndex) => { const role = 'system'; const content = prompt.content || ''; @@ -525,14 +571,14 @@ function populateDialogueExamples(prompts, chatCompletion) { chatMessage.setName(prompt.name); if (chatCompletion.canAfford(chatMessage)) { chatCompletion.insert(chatMessage, 'dialogueExamples'); + examplesAdded++; } }); + + if (0 === examplesAdded) { + chatCompletion.removeLastFrom('dialogueExamples'); + } }); - - chatCompletion.freeBudget(newExampleChat); - - const chatExamples = chatCompletion.getMessages().getItemByIdentifier('dialogueExamples').getCollection(); - if (chatExamples.length) chatCompletion.insertAtStart(newExampleChat, 'dialogueExamples'); } } @@ -671,9 +717,10 @@ function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, ty * * @returns {Object} prompts - The prepared and merged system and user-defined prompts. */ -function preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts) { +function preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride) { const scenarioText = Scenario ? `[Circumstances and context of the dialogue: ${Scenario}]` : ''; - const charPersonalityText = charPersonality ? `[${name2}'s personality: ${charPersonality}]` : ''; + const charPersonalityText = charPersonality ? `[${name2}'s personality: ${charPersonality}]` : '' + const groupNudge = `[Write the next reply only as ${name2}]`; // Create entries for system prompts const systemPrompts = [ @@ -687,7 +734,8 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world { role: 'system', content: oai_settings.nsfw_avoidance_prompt, identifier: 'nsfwAvoidance' }, { role: 'system', content: oai_settings.impersonation_prompt, identifier: 'impersonate' }, { role: 'system', content: quietPrompt, identifier: 'quietPrompt' }, - { role: 'system', content: bias, identifier: 'bias' } + { role: 'system', content: bias, identifier: 'bias' }, + { role: 'system', content: groupNudge, identifier: 'groupNudge' } ]; // Tavern Extras - Summary @@ -724,7 +772,6 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world }); // Apply character-specific main prompt - const systemPromptOverride = promptManager.activeCharacter.data?.system_prompt ?? null; const systemPrompt = prompts.get('main') ?? null; if (systemPromptOverride && systemPrompt) { const mainOriginalContent = systemPrompt.content; @@ -734,7 +781,6 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world } // Apply character-specific jailbreak - const jailbreakPromptOverride = promptManager.activeCharacter.data?.post_history_instructions ?? null; const jailbreakPrompt = prompts.get('jailbreak') ?? null; if (jailbreakPromptOverride && jailbreakPrompt) { const jbOriginalContent = jailbreakPrompt.content; @@ -778,7 +824,9 @@ function prepareOpenAIMessages({ type, quietPrompt, extensionPrompts, - cyclePrompt + cyclePrompt, + systemPromptOverride, + jailbreakPromptOverride, } = {}, dryRun) { // Without a character selected, there is no way to accurately calculate tokens if (!promptManager.activeCharacter && dryRun) return [null, false]; @@ -791,7 +839,7 @@ function prepareOpenAIMessages({ try { // Merge markers and ordered user prompts with system prompts - const prompts = preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts); + const prompts = preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride); // Fill the chat completion with as much context as the budget allows populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, type, cyclePrompt }); @@ -806,8 +854,10 @@ function prepareOpenAIMessages({ promptManager.error = 'The name of at least one character contained whitespaces or special characters. Please check your user and character name.'; } else { toastr.error('An unknown error occurred while counting tokens. Further information may be available in console.') - chatCompletion.log('Unexpected error:'); + chatCompletion.log('----- Unexpected error while preparing prompts -----'); chatCompletion.log(error); + chatCompletion.log(error.stack); + chatCompletion.log('----------------------------------------------------'); } } finally { // Pass chat completion to prompt manager for inspection @@ -966,6 +1016,8 @@ function getChatCompletionModel() { return ''; case chat_completion_sources.OPENROUTER: return oai_settings.openrouter_model !== openrouter_website_model ? oai_settings.openrouter_model : null; + case chat_completion_sources.AI21: + return oai_settings.ai21_model; default: throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`); } @@ -1047,9 +1099,18 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) { const isClaude = oai_settings.chat_completion_source == chat_completion_sources.CLAUDE; const isOpenRouter = oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER; const isScale = oai_settings.chat_completion_source == chat_completion_sources.SCALE; + const isAI21 = oai_settings.chat_completion_source == chat_completion_sources.AI21; const isTextCompletion = oai_settings.chat_completion_source == chat_completion_sources.OPENAI && (oai_settings.openai_model.startsWith('text-') || oai_settings.openai_model.startsWith('code-')); - const stream = type !== 'quiet' && oai_settings.stream_openai && !isScale; const isQuiet = type === 'quiet'; + const stream = oai_settings.stream_openai && !isQuiet && !isScale && !isAI21; + + if (isAI21) { + const joinedMsgs = openai_msgs_tosend.reduce((acc, obj) => { + const prefix = prefixMap[obj.role]; + return acc + (prefix ? (selected_group ? "\n" : prefix + " ") : "") + obj.content + "\n"; + }, ""); + openai_msgs_tosend = substituteParams(joinedMsgs); + } // If we're using the window.ai extension, use that instead // Doesn't support logit bias yet @@ -1089,9 +1150,9 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) { if (isClaude) { generate_data['use_claude'] = true; generate_data['top_k'] = parseFloat(oai_settings.top_k_openai); - + generate_data['exclude_assistant'] = oai_settings.exclude_assistant; // Don't add a prefill on quiet gens (summarization) - if (!isQuiet) { + if (!isQuiet && !oai_settings.exclude_assistant) { generate_data['assistant_prefill'] = substituteParams(oai_settings.assistant_prefill); } } @@ -1106,6 +1167,13 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) { generate_data['api_url_scale'] = oai_settings.api_url_scale; } + if (isAI21) { + generate_data['use_ai21'] = true; + generate_data['top_k'] = parseFloat(oai_settings.top_k_openai); + generate_data['count_pen'] = parseFloat(oai_settings.count_pen); + generate_data['stop_tokens'] = [name1 + ':', oai_settings.new_chat_prompt, oai_settings.new_group_chat_prompt]; + } + const generate_url = '/generate_openai'; const response = await fetch(generate_url, { method: 'POST', @@ -1296,6 +1364,7 @@ class TokenHandler { } function countTokens(messages, full = false) { + let shouldTokenizeAI21 = oai_settings.chat_completion_source === chat_completion_sources.AI21 && oai_settings.use_ai21_tokenizer; let chatId = 'undefined'; try { @@ -1321,19 +1390,19 @@ function countTokens(messages, full = false) { for (const message of messages) { const model = getTokenizerModel(); - const hash = getStringHash(message.content); + const hash = getStringHash(JSON.stringify(message)); const cacheKey = `${model}-${hash}`; const cachedCount = tokenCache[chatId][cacheKey]; if (typeof cachedCount === 'number') { token_count += cachedCount; } - else { + else { jQuery.ajax({ async: false, type: 'POST', // - url: `/tokenize_openai?model=${model}`, + url: shouldTokenizeAI21 ? '/tokenize_ai21' : `/tokenize_openai?model=${model}`, data: JSON.stringify([message]), dataType: "json", contentType: "application/json", @@ -1393,8 +1462,8 @@ class Message { this.role = role; this.content = content; - if (this.content) { - this.tokens = tokenHandler.count({ role: this.role, content: this.content }) + if (typeof this.content === 'string') { + this.tokens = tokenHandler.count({ role: this.role, content: this.content }); } else { this.tokens = 0; } @@ -1402,6 +1471,7 @@ class Message { setName(name) { this.name = name; + this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name }); } /** @@ -1613,6 +1683,21 @@ class ChatCompletion { } } + + /** + * Remove the last item of the collection + * + * @param identifier + */ + removeLastFrom(identifier) { + const index = this.findMessageIndex(identifier); + const message = this.messages.collection[index].collection.pop(); + + this.increaseTokenBudgetBy(message.getTokens()); + + this.log(`Removed ${message.identifier} from ${identifier}. Remaining tokens: ${this.tokenBudget}`); + } + /** * Checks if the token budget can afford the tokens of the specified message. * @@ -1849,6 +1934,7 @@ function loadOpenAISettings(data, settings) { oai_settings.temp_openai = settings.temp_openai ?? default_settings.temp_openai; oai_settings.freq_pen_openai = settings.freq_pen_openai ?? default_settings.freq_pen_openai; oai_settings.pres_pen_openai = settings.pres_pen_openai ?? default_settings.pres_pen_openai; + oai_settings.count_pen = settings.count_pen ?? default_settings.count_pen; oai_settings.top_p_openai = settings.top_p_openai ?? default_settings.top_p_openai; oai_settings.top_k_openai = settings.top_k_openai ?? default_settings.top_k_openai; oai_settings.stream_openai = settings.stream_openai ?? default_settings.stream_openai; @@ -1864,6 +1950,7 @@ function loadOpenAISettings(data, settings) { oai_settings.claude_model = settings.claude_model ?? default_settings.claude_model; oai_settings.windowai_model = settings.windowai_model ?? default_settings.windowai_model; oai_settings.openrouter_model = settings.openrouter_model ?? default_settings.openrouter_model; + oai_settings.ai21_model = settings.ai21_model ?? default_settings.ai21_model; oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source; oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale; oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models; @@ -1882,7 +1969,8 @@ function loadOpenAISettings(data, settings) { if (settings.wrap_in_quotes !== undefined) oai_settings.wrap_in_quotes = !!settings.wrap_in_quotes; if (settings.names_in_completion !== undefined) oai_settings.names_in_completion = !!settings.names_in_completion; if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model; - + if (settings.use_ai21_tokenizer !== undefined) { oai_settings.use_ai21_tokenizer = !!settings.use_ai21_tokenizer; oai_settings.use_ai21_tokenizer ? ai21_max = 8191 : ai21_max = 9200; } + if (settings.exclude_assistant !== undefined) oai_settings.exclude_assistant = !!settings.exclude_assistant; $('#stream_toggle').prop('checked', oai_settings.stream_openai); $('#api_url_scale').val(oai_settings.api_url_scale); $('#openai_proxy_password').val(oai_settings.proxy_password); @@ -1894,6 +1982,8 @@ function loadOpenAISettings(data, settings) { $(`#model_claude_select option[value="${oai_settings.claude_model}"`).attr('selected', true); $('#model_windowai_select').val(oai_settings.windowai_model); $(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).attr('selected', true); + $('#model_ai21_select').val(oai_settings.ai21_model); + $(`#model_ai21_select option[value="${oai_settings.ai21_model}"`).attr('selected', true); $('#openai_max_context').val(oai_settings.openai_max_context); $('#openai_max_context_counter').text(`${oai_settings.openai_max_context}`); $('#model_openrouter_select').val(oai_settings.openrouter_model); @@ -1909,7 +1999,8 @@ function loadOpenAISettings(data, settings) { $('#legacy_streaming').prop('checked', oai_settings.legacy_streaming); $('#openai_show_external_models').prop('checked', oai_settings.show_external_models); $('#openai_external_category').toggle(oai_settings.show_external_models); - + $('#use_ai21_tokenizer').prop('checked', oai_settings.use_ai21_tokenizer); + $('#exclude_assistant').prop('checked', oai_settings.exclude_assistant); if (settings.impersonation_prompt !== undefined) oai_settings.impersonation_prompt = settings.impersonation_prompt; $('#impersonation_prompt_textarea').val(oai_settings.impersonation_prompt); @@ -1932,6 +2023,9 @@ function loadOpenAISettings(data, settings) { $('#pres_pen_openai').val(oai_settings.pres_pen_openai); $('#pres_pen_counter_openai').text(Number(oai_settings.pres_pen_openai).toFixed(2)); + $('#count_pen').val(oai_settings.count_pen); + $('#count_pen_counter').text(Number(oai_settings.count_pen).toFixed(2)); + $('#top_p_openai').val(oai_settings.top_p_openai); $('#top_p_counter_openai').text(Number(oai_settings.top_p_openai).toFixed(2)); @@ -1974,7 +2068,7 @@ async function getStatusOpen() { return resultCheckStatusOpen(); } - if (oai_settings.chat_completion_source == chat_completion_sources.SCALE || oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) { + if (oai_settings.chat_completion_source == chat_completion_sources.SCALE || oai_settings.chat_completion_source == chat_completion_sources.CLAUDE || oai_settings.chat_completion_source == chat_completion_sources.AI21) { let status = 'Unable to verify key; press "Test Message" to validate.'; setOnlineStatus(status); return resultCheckStatusOpen(); @@ -2071,9 +2165,11 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) { claude_model: settings.claude_model, windowai_model: settings.windowai_model, openrouter_model: settings.openrouter_model, + ai21_model: settings.ai21_model, temperature: settings.temp_openai, frequency_penalty: settings.freq_pen_openai, presence_penalty: settings.pres_pen_openai, + count_penalty: settings.count_pen, top_p: settings.top_p_openai, top_k: settings.top_k_openai, openai_max_context: settings.openai_max_context, @@ -2101,6 +2197,8 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) { api_url_scale: settings.api_url_scale, show_external_models: settings.show_external_models, assistant_prefill: settings.assistant_prefill, + use_ai21_tokenizer: settings.use_ai21_tokenizer, + exclude_assistant: settings.exclude_assistant, }; const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, { @@ -2286,7 +2384,11 @@ async function onExportPresetClick() { return; } - const preset = openai_settings[openai_setting_names[oai_settings.preset_settings_openai]]; + const preset = deepClone(openai_settings[openai_setting_names[oai_settings.preset_settings_openai]]); + + delete preset.reverse_proxy; + delete preset.proxy_password; + const presetJsonString = JSON.stringify(preset, null, 4); download(presetJsonString, oai_settings.preset_settings_openai, 'application/json'); } @@ -2400,6 +2502,7 @@ function onSettingsPresetChange() { temperature: ['#temp_openai', 'temp_openai', false], frequency_penalty: ['#freq_pen_openai', 'freq_pen_openai', false], presence_penalty: ['#pres_pen_openai', 'pres_pen_openai', false], + count_penalty: ['#count_pen', 'count_pen', false], top_p: ['#top_p_openai', 'top_p_openai', false], top_k: ['#top_k_openai', 'top_k_openai', false], max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true], @@ -2407,6 +2510,7 @@ function onSettingsPresetChange() { claude_model: ['#model_claude_select', 'claude_model', false], windowai_model: ['#model_windowai_select', 'windowai_model', false], openrouter_model: ['#model_openrouter_select', 'openrouter_model', false], + ai21_model: ['#model_ai21_select', 'ai21_model', false], openai_max_context: ['#openai_max_context', 'openai_max_context', false], openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false], wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true], @@ -2430,6 +2534,8 @@ function onSettingsPresetChange() { show_external_models: ['#openai_show_external_models', 'show_external_models', true], proxy_password: ['#openai_proxy_password', 'proxy_password', false], assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false], + use_ai21_tokenizer: ['#use_ai21_tokenizer', 'use_ai21_tokenizer', false], + exclude_assistant: ['#exclude_assistant', 'exclude_assistant', false], }; const presetName = $('#settings_perset_openai').find(":selected").text(); @@ -2554,6 +2660,11 @@ async function onModelChange() { oai_settings.openrouter_model = value; } + if ($(this).is('#model_ai21_select')) { + console.log('AI21 model changed to', value); + oai_settings.ai21_model = value; + } + if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) { if (oai_settings.max_context_unlocked) { $('#openai_max_context').attr('max', unlocked_max); @@ -2640,6 +2751,38 @@ async function onModelChange() { $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); } + if (oai_settings.chat_completion_source == chat_completion_sources.AI21) { + if (oai_settings.max_context_unlocked) { + $('#openai_max_context').attr('max', unlocked_max); + } else { + $('#openai_max_context').attr('max', ai21_max); + } + + oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max'))); + $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); + + oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai); + $('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input'); + + oai_settings.freq_pen_openai = Math.min(j2_max_freq, oai_settings.freq_pen_openai < 0 ? 0 : oai_settings.freq_pen_openai); + $('#freq_pen_openai').attr('min', 0).attr('max', j2_max_freq).val(oai_settings.freq_pen_openai).trigger('input'); + + oai_settings.pres_pen_openai = Math.min(j2_max_pres, oai_settings.pres_pen_openai < 0 ? 0 : oai_settings.pres_pen_openai); + $('#pres_pen_openai').attr('min', 0).attr('max', j2_max_pres).val(oai_settings.pres_pen_openai).trigger('input'); + + oai_settings.top_k_openai = Math.min(j2_max_topk, oai_settings.top_k_openai); + $('#top_k_openai').attr('max', j2_max_topk).val(oai_settings.top_k_openai).trigger('input'); + } else if (oai_settings.chat_completion_source != chat_completion_sources.AI21) { + oai_settings.freq_pen_openai = Math.min(2.0, oai_settings.freq_pen_openai); + $('#freq_pen_openai').attr('min', -2.0).attr('max', 2.0).val(oai_settings.freq_pen_openai).trigger('input'); + + oai_settings.freq_pen_openai = Math.min(2.0, oai_settings.pres_pen_openai); + $('#pres_pen_openai').attr('min', -2.0).attr('max', 2.0).val(oai_settings.freq_pen_openai).trigger('input'); + + oai_settings.top_k_openai = Math.min(200, oai_settings.top_k_openai); + $('#top_k_openai').attr('max', 200).val(oai_settings.top_k_openai).trigger('input'); + } + saveSettingsDebounced(); eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value); } @@ -2730,6 +2873,19 @@ async function onConnectButtonClick(e) { } } + if (oai_settings.chat_completion_source == chat_completion_sources.AI21) { + const api_key_ai21 = $('#api_key_ai21').val().trim(); + + if (api_key_ai21.length) { + await writeSecret(SECRET_KEYS.AI21, api_key_ai21); + } + + if (!secret_state[SECRET_KEYS.AI21]) { + console.log('No secret key saved for AI21'); + return; + } + } + $("#api_loading_openai").css("display", 'inline-block'); $("#api_button_openai").css("display", 'none'); saveSettingsDebounced(); @@ -2759,11 +2915,17 @@ function toggleChatCompletionForms() { else if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) { $('#model_openrouter_select').trigger('change'); } - + else if (oai_settings.chat_completion_source == chat_completion_sources.AI21) { + $('#model_ai21_select').trigger('change'); + } $('[data-source]').each(function () { const validSources = $(this).data('source').split(','); $(this).toggle(validSources.includes(oai_settings.chat_completion_source)); }); + + if (chat_completion_sources.CLAUDE == oai_settings.chat_completion_source) { + $('#claude_assistant_prefill_block').toggle(!oai_settings.exclude_assistant); + } } async function testApiConnection() { @@ -2817,7 +2979,12 @@ $(document).ready(async function () { oai_settings.pres_pen_openai = $(this).val(); $('#pres_pen_counter_openai').text(Number($(this).val()).toFixed(2)); saveSettingsDebounced(); + }); + $(document).on('input', '#count_pen', function () { + oai_settings.count_pen = $(this).val(); + $('#count_pen_counter').text(Number($(this).val()).toFixed(2)); + saveSettingsDebounced(); }); $(document).on('input', '#top_p_openai', function () { @@ -2855,6 +3022,20 @@ $(document).ready(async function () { saveSettingsDebounced(); }); + $('#use_ai21_tokenizer').on('change', function () { + oai_settings.use_ai21_tokenizer = !!$('#use_ai21_tokenizer').prop('checked'); + oai_settings.use_ai21_tokenizer ? ai21_max = 8191 : ai21_max = 9200; + oai_settings.openai_max_context = Math.min(ai21_max, oai_settings.openai_max_context); + $('#openai_max_context').attr('max', ai21_max).val(oai_settings.openai_max_context).trigger('input'); + saveSettingsDebounced(); + }); + + $('#exclude_assistant').on('change', function () { + oai_settings.exclude_assistant = !!$('#exclude_assistant').prop('checked'); + $('#claude_assistant_prefill_block').toggle(!oai_settings.exclude_assistant); + saveSettingsDebounced(); + }); + $('#names_in_completion').on('change', function () { oai_settings.names_in_completion = !!$('#names_in_completion').prop('checked'); saveSettingsDebounced(); @@ -3022,6 +3203,7 @@ $(document).ready(async function () { $("#model_windowai_select").on("change", onModelChange); $("#model_scale_select").on("change", onModelChange); $("#model_openrouter_select").on("change", onModelChange); + $("#model_ai21_select").on("change", onModelChange); $("#settings_perset_openai").on("change", onSettingsPresetChange); $("#new_oai_preset").on("click", onNewPresetClick); $("#delete_oai_preset").on("click", onDeletePresetClick); diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 9a696850f..4cf06bbe1 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -12,8 +12,6 @@ import { event_types, getCurrentChatId, printCharacters, - name1, - name2, setCharacterId, setEditedMessageId } from "../script.js"; @@ -21,8 +19,8 @@ import { isMobile, initMovingUI } from "./RossAscends-mods.js"; import { groups, resetSelectedGroup, - selected_group, } from "./group-chats.js"; +import { loadInstructMode } from "./instruct-mode.js"; import { registerSlashCommand } from "./slash-commands.js"; @@ -44,9 +42,7 @@ export { export const MAX_CONTEXT_DEFAULT = 4096; const MAX_CONTEXT_UNLOCKED = 65536; -const defaultStoryString = `{{#if description}}{{description}}{{/if}} -{{#if personality}}{{personality}}{{/if}} -{{#if scenario}}Scenario: {{scenario}}{{/if}}`; +const defaultStoryString = "{{#if system}}{{system}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}"; const defaultExampleSeparator = '***'; const defaultChatStart = '***'; @@ -97,6 +93,7 @@ let power_user = { collapse_newlines: false, pygmalion_formatting: pygmalion_options.AUTO, pin_examples: false, + strip_examples: false, trim_sentences: false, include_newline: false, always_force_name2: false, @@ -165,6 +162,7 @@ let power_user = { trim_spaces: true, relaxed_api_urls: false, + default_instruct: '', instruct: { enabled: false, wrap: true, @@ -179,6 +177,7 @@ let power_user = { separator_sequence: '', macro: false, names_force_groups: true, + activation_regex: '', }, context: { @@ -205,7 +204,6 @@ let power_user = { let themes = []; let movingUIPresets = []; -let instruct_presets = []; let context_presets = []; const storage_keys = { @@ -665,9 +663,6 @@ function loadPowerUserSettings(settings, data) { movingUIPresets = data.movingUIPresets; } - if (data.instruct !== undefined) { - instruct_presets = data.instruct; - } if (data.context !== undefined) { context_presets = data.context; @@ -731,6 +726,7 @@ function loadPowerUserSettings(settings, data) { $("#spoiler_free_mode").prop("checked", power_user.spoiler_free_mode); $("#collapse-newlines-checkbox").prop("checked", power_user.collapse_newlines); $("#pin-examples-checkbox").prop("checked", power_user.pin_examples); + $("#remove-examples-checkbox").prop("checked", power_user.strip_examples); $("#always-force-name2-checkbox").prop("checked", power_user.always_force_name2); $("#trim_sentences_checkbox").prop("checked", power_user.trim_sentences); $("#include_newline_checkbox").prop("checked", power_user.include_newline); @@ -802,7 +798,7 @@ function loadPowerUserSettings(settings, data) { $(`#character_sort_order option[data-order="${power_user.sort_order}"][data-field="${power_user.sort_field}"]`).prop("selected", true); reloadMarkdownProcessor(power_user.render_formulas); - loadInstructMode(); + loadInstructMode(data); loadContextSettings(); loadMaxContextUnlocked(); switchWaifuMode(); @@ -812,7 +808,7 @@ function loadPowerUserSettings(settings, data) { } async function loadCharListState() { - if (document.getElementById('CharID0') !== null) { + if (document.querySelector('.character_select') !== null) { console.debug('setting charlist state to...') if (power_user.charListGrid === true) { console.debug('..to grid') @@ -926,74 +922,6 @@ function loadContextSettings() { }); } -function loadInstructMode() { - const controls = [ - { id: "instruct_enabled", property: "enabled", isCheckbox: true }, - { id: "instruct_wrap", property: "wrap", isCheckbox: true }, - { id: "instruct_system_prompt", property: "system_prompt", isCheckbox: false }, - { id: "instruct_system_sequence", property: "system_sequence", isCheckbox: false }, - { id: "instruct_separator_sequence", property: "separator_sequence", isCheckbox: false }, - { id: "instruct_input_sequence", property: "input_sequence", isCheckbox: false }, - { id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false }, - { id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false }, - { id: "instruct_names", property: "names", isCheckbox: true }, - { id: "instruct_macro", property: "macro", isCheckbox: true }, - { id: "instruct_names_force_groups", property: "names_force_groups", isCheckbox: true }, - { id: "instruct_last_output_sequence", property: "last_output_sequence", isCheckbox: false }, - ]; - - if (power_user.instruct.names_force_groups === undefined) { - power_user.instruct.names_force_groups = true; - } - - controls.forEach(control => { - const $element = $(`#${control.id}`); - - if (control.isCheckbox) { - $element.prop('checked', power_user.instruct[control.property]); - } else { - $element.val(power_user.instruct[control.property]); - } - - $element.on('input', function () { - power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val(); - saveSettingsDebounced(); - }); - }); - - instruct_presets.forEach((preset) => { - const name = preset.name; - const option = document.createElement('option'); - option.value = name; - option.innerText = name; - option.selected = name === power_user.instruct.preset; - $('#instruct_presets').append(option); - }); - - $('#instruct_presets').on('change', function () { - const name = $(this).find(':selected').val(); - const preset = instruct_presets.find(x => x.name === name); - - if (!preset) { - return; - } - - power_user.instruct.preset = name; - controls.forEach(control => { - if (preset[control.property] !== undefined) { - power_user.instruct[control.property] = preset[control.property]; - const $element = $(`#${control.id}`); - - if (control.isCheckbox) { - $element.prop('checked', power_user.instruct[control.property]).trigger('input'); - } else { - $element.val(power_user.instruct[control.property]).trigger('input'); - } - } - }); - }); -} - export function fuzzySearchCharacters(searchValue) { const fuse = new Fuse(characters, { keys: [ @@ -1050,58 +978,6 @@ export function renderStoryString(params) { } } -export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) { - let includeNames = isNarrator ? false : power_user.instruct.names; - - if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) { - includeNames = true; - } - - let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence; - - if (power_user.instruct.macro) { - sequence = substituteParams(sequence, name1, name2); - } - - const separator = power_user.instruct.wrap ? '\n' : ''; - const separatorSequence = power_user.instruct.separator_sequence && !isUser - ? power_user.instruct.separator_sequence - : (power_user.instruct.wrap ? '\n' : ''); - const textArray = includeNames ? [sequence, `${name}: ${mes}`, separatorSequence] : [sequence, mes, separatorSequence]; - const text = textArray.filter(x => x).join(separator); - return text; -} - -export function formatInstructStoryString(story, systemPrompt) { - // If the character has a custom system prompt AND user has it preferred, use that instead of the default - systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : power_user.instruct.system_prompt; - const sequence = power_user.instruct.system_sequence || ''; - const prompt = substituteParams(systemPrompt, name1, name2, power_user.instruct.system_prompt) || ''; - const separator = power_user.instruct.wrap ? '\n' : ''; - const textArray = [sequence, prompt + '\n' + story]; - const text = textArray.filter(x => x).join(separator); - return text; -} - -export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) { - const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups); - const getOutputSequence = () => power_user.instruct.last_output_sequence || power_user.instruct.output_sequence; - let sequence = isImpersonate ? power_user.instruct.input_sequence : getOutputSequence(); - - if (power_user.instruct.macro) { - sequence = substituteParams(sequence, name1, name2); - } - - const separator = power_user.instruct.wrap ? '\n' : ''; - let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence); - - if (!isImpersonate && promptBias) { - text += (includeNames ? promptBias : (separator + promptBias)); - } - - return text.trimEnd(); -} - const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a); const compareFunc = (first, second) => { if (power_user.sort_order == 'random') { @@ -1677,10 +1553,27 @@ $(document).ready(() => { }); $("#pin-examples-checkbox").change(function () { + if ($(this).prop("checked")) { + $("#remove-examples-checkbox").prop("checked", false).prop("disabled", true); + power_user.strip_examples = false; + } else { + $("#remove-examples-checkbox").prop("disabled", false); + } power_user.pin_examples = !!$(this).prop("checked"); saveSettingsDebounced(); }); + $("#remove-examples-checkbox").change(function () { + if ($(this).prop("checked")) { + $("#pin-examples-checkbox").prop("checked", false).prop("disabled", true); + power_user.pin_examples = false; + } else { + $("#pin-examples-checkbox").prop("disabled", false); + } + power_user.strip_examples = !!$(this).prop("checked"); + saveSettingsDebounced(); + }); + // include newline is the child of trim sentences // if include newline is checked, trim sentences must be checked // if trim sentences is unchecked, include newline must be unchecked diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index 74a3d187a..4ebb55418 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -16,13 +16,15 @@ import { this_chid, } from "../script.js"; import { groups, selected_group } from "./group-chats.js"; +import { instruct_presets } from "./instruct-mode.js"; import { kai_settings } from "./kai-settings.js"; +import { power_user } from "./power-user.js"; import { textgenerationwebui_preset_names, textgenerationwebui_presets, textgenerationwebui_settings, } from "./textgen-settings.js"; -import { download, parseJsonFile, waitUntilCondition } from "./utils.js"; +import { deepClone, download, parseJsonFile, waitUntilCondition } from "./utils.js"; const presetManagers = {}; @@ -55,8 +57,10 @@ function autoSelectPreset() { } } -function getPresetManager() { - const apiId = main_api == 'koboldhorde' ? 'kobold' : main_api; +function getPresetManager(apiId) { + if (!apiId) { + apiId = main_api == 'koboldhorde' ? 'kobold' : main_api; + } if (!Object.keys(presetManagers).includes(apiId)) { return null; @@ -162,6 +166,10 @@ class PresetManager { presets = textgenerationwebui_presets; preset_names = textgenerationwebui_preset_names; break; + case "instruct": + presets = instruct_presets; + preset_names = instruct_presets.map(x => x.name); + break; default: console.warn(`Unknown API ID ${this.apiId}`); } @@ -169,12 +177,20 @@ class PresetManager { return { presets, preset_names }; } + isKeyedApi() { + return this.apiId == "textgenerationwebui" || this.apiId == "instruct"; + } + + isNonGenericApi() { + return this.apiId == "instruct"; + } + updateList(name, preset) { const { presets, preset_names } = this.getPresetList(); - const presetExists = this.apiId == "textgenerationwebui" ? preset_names.includes(name) : Object.keys(preset_names).includes(name); + const presetExists = this.isKeyedApi() ? preset_names.includes(name) : Object.keys(preset_names).includes(name); if (presetExists) { - if (this.apiId == "textgenerationwebui") { + if (this.isKeyedApi()) { presets[preset_names.indexOf(name)] = preset; $(this.select).find(`option[value="${name}"]`).prop('selected', true); $(this.select).val(name).trigger("change"); @@ -189,8 +205,8 @@ class PresetManager { else { presets.push(preset); const value = presets.length - 1; - // ooba is reversed - if (this.apiId == "textgenerationwebui") { + + if (this.isKeyedApi()) { preset_names[value] = name; const option = $('', { value: name, text: name, selected: true }); $(this.select).append(option); @@ -214,6 +230,10 @@ class PresetManager { return nai_settings; case "textgenerationwebui": return textgenerationwebui_settings; + case "instruct": + const preset = deepClone(power_user.instruct); + preset['name'] = power_user.instruct.preset; + return preset; default: console.warn(`Unknown API ID ${apiId}`); return {}; @@ -229,6 +249,7 @@ class PresetManager { 'streaming_novel', 'nai_preamble', 'model_novel', + "enabled", ]; const settings = Object.assign({}, getSettingsByApiId(this.apiId)); @@ -238,8 +259,10 @@ class PresetManager { } } - settings['genamt'] = amount_gen; - settings['max_length'] = max_context; + if (!this.isNonGenericApi()) { + settings['genamt'] = amount_gen; + settings['max_length'] = max_context; + } return settings; } @@ -256,7 +279,7 @@ class PresetManager { $(this.select).find(`option[value="${value}"]`).remove(); - if (this.apiId == "textgenerationwebui") { + if (this.isKeyedApi()) { preset_names.splice(preset_names.indexOf(value), 1); } else { delete preset_names[nameToDelete]; @@ -289,9 +312,11 @@ jQuery(async () => { eventSource.on(event_types.CHAT_CHANGED, autoSelectPreset); registerPresetManagers(); $(document).on("click", "[data-preset-manager-update]", async function () { - const presetManager = getPresetManager(); + const apiId = $(this).data("preset-manager-update"); + const presetManager = getPresetManager(apiId); if (!presetManager) { + console.warn(`Preset Manager not found for API: ${apiId}`); return; } @@ -299,9 +324,11 @@ jQuery(async () => { }); $(document).on("click", "[data-preset-manager-new]", async function () { - const presetManager = getPresetManager(); + const apiId = $(this).data("preset-manager-new"); + const presetManager = getPresetManager(apiId); if (!presetManager) { + console.warn(`Preset Manager not found for API: ${apiId}`); return; } @@ -309,9 +336,11 @@ jQuery(async () => { }); $(document).on("click", "[data-preset-manager-export]", async function () { - const presetManager = getPresetManager(); + const apiId = $(this).data("preset-manager-export"); + const presetManager = getPresetManager(apiId); if (!presetManager) { + console.warn(`Preset Manager not found for API: ${apiId}`); return; } @@ -323,13 +352,16 @@ jQuery(async () => { }); $(document).on("click", "[data-preset-manager-import]", async function () { - $('[data-preset-manager-file]').trigger('click'); + const apiId = $(this).data("preset-manager-import"); + $(`[data-preset-manager-file="${apiId}"]`).trigger('click'); }); $(document).on("change", "[data-preset-manager-file]", async function (e) { - const presetManager = getPresetManager(); + const apiId = $(this).data("preset-manager-file"); + const presetManager = getPresetManager(apiId); if (!presetManager) { + console.warn(`Preset Manager not found for API: ${apiId}`); return; } @@ -348,9 +380,11 @@ jQuery(async () => { }); $(document).on("click", "[data-preset-manager-delete]", async function () { - const presetManager = getPresetManager(); + const apiId = $(this).data("preset-manager-delete"); + const presetManager = getPresetManager(apiId); if (!presetManager) { + console.warn(`Preset Manager not found for API: ${apiId}`); return; } diff --git a/public/scripts/secrets.js b/public/scripts/secrets.js index 00203a106..202870c50 100644 --- a/public/scripts/secrets.js +++ b/public/scripts/secrets.js @@ -8,6 +8,7 @@ export const SECRET_KEYS = { CLAUDE: 'api_key_claude', OPENROUTER: 'api_key_openrouter', SCALE: 'api_key_scale', + AI21: 'api_key_ai21', } const INPUT_MAP = { @@ -18,6 +19,7 @@ const INPUT_MAP = { [SECRET_KEYS.CLAUDE]: '#api_key_claude', [SECRET_KEYS.OPENROUTER]: '#api_key_openrouter', [SECRET_KEYS.SCALE]: '#api_key_scale', + [SECRET_KEYS.AI21]: '#api_key_ai21', } async function clearSecret() { diff --git a/public/scripts/tags.js b/public/scripts/tags.js index cd8339358..a6887c7c6 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -285,8 +285,8 @@ function appendTagToList(listElement, tag, { removable, selectable, action, isGe tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon); } - if (tag.excluded) { - isGeneralList ? $(tagElement).addClass('excluded') : $(listElement).closest('.character_select, .group_select').addClass('hiddenByTag'); + if (tag.excluded && isGeneralList) { + $(tagElement).addClass('excluded'); } if (selectable) { diff --git a/public/scripts/utils.js b/public/scripts/utils.js index 8673427aa..5b2ee1de6 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -1,4 +1,5 @@ import { getContext } from "./extensions.js"; +import { getRequestHeaders } from "../script.js"; export function onlyUnique(value, index, array) { return array.indexOf(value) === index; @@ -554,6 +555,48 @@ export function extractDataFromPng(data, identifier = 'chara') { } } +/** + * Sends a base64 encoded image to the backend to be saved as a file. + * + * @param {string} base64Data - The base64 encoded image data. + * @param {string} characterName - The character name to determine the sub-directory for saving. + * @param {string} ext - The file extension for the image (e.g., 'jpg', 'png', 'webp'). + * + * @returns {Promise} - Resolves to the saved image's path on the server. + * Rejects with an error if the upload fails. + */ +export async function saveBase64AsFile(base64Data, characterName, filename = "", ext) { + // Construct the full data URL + const format = ext; // Extract the file extension (jpg, png, webp) + const dataURL = `data:image/${format};base64,${base64Data}`; + + // Prepare the request body + const requestBody = { + image: dataURL, + ch_name: characterName, + filename: filename + }; + + // Send the data URL to your backend using fetch + const response = await fetch('/uploadimage', { + method: 'POST', + body: JSON.stringify(requestBody), + headers: { + ...getRequestHeaders(), + 'Content-Type': 'application/json' + }, + }); + + // If the response is successful, get the saved image path from the server's response + if (response.ok) { + const responseData = await response.json(); + return responseData.path; + } else { + const errorData = await response.json(); + throw new Error(errorData.error || 'Failed to upload the image to the server'); + } +} + export function createThumbnail(dataUrl, maxWidth, maxHeight) { return new Promise((resolve, reject) => { const img = new Image(); diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 525bb46e0..22c33f219 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -149,9 +149,6 @@ function setWorldInfoSettings(settings, data) { }); $("#world_editor_select").trigger("change"); - - // Update settings - saveSettingsDebounced(); } // World Info Editor @@ -467,7 +464,7 @@ function appendWorldEntry(name, data, entry) { const contentInput = template.find('textarea[name="content"]'); contentInput.data("uid", entry.uid); - contentInput.on("input", function () { + contentInput.on("input", function (_, { skipCount } = {}) { const uid = $(this).data("uid"); const value = $(this).val(); data.entries[uid].content = value; @@ -475,12 +472,25 @@ function appendWorldEntry(name, data, entry) { setOriginalDataValue(data, uid, "content", data.entries[uid].content); saveWorldInfo(name, data); + if (skipCount) { + return; + } + // count tokens countTokensDebounced(this, value); }); - contentInput.val(entry.content).trigger("input"); + contentInput.val(entry.content).trigger("input", { skipCount: true }); //initScrollHeight(contentInput); + template.find('.inline-drawer-toggle').on('click', function () { + const counter = template.find(".world_entry_form_token_counter"); + + if (counter.data('first-run')) { + counter.data('first-run', false); + countTokensDebounced(contentInput, contentInput.val()); + } + }); + // selective const selectiveInput = template.find('input[name="selective"]'); selectiveInput.data("uid", entry.uid); diff --git a/public/style.css b/public/style.css index 535aa5768..bc4d0454a 100644 --- a/public/style.css +++ b/public/style.css @@ -35,7 +35,10 @@ --cobalt30a: rgba(100, 100, 255, 0.3); --greyCAIbg: rgb(36, 36, 37); --ivory: rgb(220, 220, 210); - --golden: rgba(212, 175, 55, 1); + --golden: rgb(248, 211, 0); + --warning: rgba(255, 0, 0, 0.9); + --active: rgb(88, 182, 0); + --preferred: rgb(244, 67, 54); /*Default Theme, will be changed by ToolCool Color Picker*/ @@ -208,58 +211,6 @@ table.responsiveTable { padding: 0.25em; } -.text_warning { - color: rgb(220 173 16); -} - -.text_danger { - color: var(--fullred); -} - -.m-t-1 { - margin-top: 1em; -} - -.m-t-2 { - margin-top: 2em; -} - -.m-t-3 { - margin-top: 3em; -} - -.m-t-4 { - margin-top: 4em; -} - -.m-t-5 { - margin-top: 5em; -} - -.m-b-1 { - margin-bottom: 1em; -} - -.m-b-2 { - margin-bottom: 2em; -} - -.m-b-3 { - margin-bottom: 3em; -} - -.m-b-4 { - margin-bottom: 4em; -} - -.m-b-5 { - margin-bottom: 5em; -} - -.tooltip { - cursor: help; -} - .mes_text p { margin-top: 0; margin-bottom: 10px; @@ -280,7 +231,7 @@ table.responsiveTable { display: block; font-size: calc(var(--mainFontSize) - 0.1rem); font-weight: 500; - color: darkgoldenrod; + color: var(--SmartThemeQuoteColor); } .mes_text i, @@ -330,9 +281,7 @@ table.responsiveTable { .mes_translate, .sd_message_gen, -.mes_narrate, -body.tts .mes[is_user="true"] .mes_narrate, -body.tts .mes[is_system="true"] .mes_narrate { +.mes_narrate { display: none; } @@ -365,12 +314,6 @@ small { min-height: unset; } -body.sd .sd_message_gen, -body.translate .mes_translate, -body.tts .mes_narrate { - display: inline-block; -} - code { font-family: Consolas, monospace; white-space: pre-wrap; @@ -494,8 +437,6 @@ hr { position: unset; } - - #sheldheader:active { cursor: grabbing; cursor: -moz-grabbing; @@ -515,7 +456,6 @@ hr { #chat { max-height: calc(100vh - 42px); - overflow-x: hidden; padding-bottom: 0; overflow-y: scroll; @@ -554,7 +494,6 @@ hr { width: 100%; margin: 0 auto 0 auto; border: 1px solid var(--grey30a); - border-radius: 0 0 10px 10px; background-color: var(--SmartThemeBlurTintColor); backdrop-filter: blur(var(--SmartThemeBlurStrength)); @@ -577,7 +516,6 @@ hr { overflow: hidden; } - #send_but_sheld>div { width: 40px; height: 40px; @@ -660,7 +598,6 @@ hr { display: flex; align-items: center; justify-content: center; - } .options-content hr { @@ -766,7 +703,6 @@ hr { padding: 0; font-family: "Noto Sans", "Noto Color Emoji", sans-serif; font-weight: 400; - } .swipe_left { @@ -887,85 +823,16 @@ hr { box-shadow: 0 0 5px var(--black50a); } -.character_select .avatar, -body.big-avatars .character_select .avatar { +.character_select .avatar { flex: unset; } -/* -.character_select .avatar img { - flex: unset; - width: 50px; - height: 50px; - width: unset; - aspect-ratio: 1 / 1; -} - -, -body.big-avatars .character_select .avatar img { - min-width: 60px; - aspect-ratio: unset; -} - - */ -body.no-hotswap .hotswap { - display: none !important; -} - -body.no-timer .mes_timer { - display: none !important; -} - -body.no-timestamps .timestamp, -body.no-mesIDDisplay .mesIDDisplay, -body.no-modelIcons .icon-svg { - display: none !important; -} - -body.big-avatars .avatar { - width: 60px; - height: 90px; - /* width: unset; */ - border-style: none; - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - /* align-self: unset; */ - overflow: visible; - border-radius: 10px; - flex: 1 -} - -body.big-avatars #user_avatar_block .avatar, -body.big-avatars #user_avatar_block .avatar_upload { - height: 90px; - width: 60px; - border-radius: 10px; -} - -body.big-avatars #user_avatar_block .avatar img { - height: 90px; - width: 60px; -} - -body.big-avatars .avatar img { - width: 60px; - height: 90px; - object-fit: cover; - object-position: center; - border: 1px solid var(--black30a); - border-radius: 10px; -} - .mes_block { padding-top: 0; padding-left: 10px; width: 100%; } - - .mes_text { font-weight: 500; line-height: calc(var(--mainFontSize) + .5rem); @@ -974,7 +841,6 @@ body.big-avatars .avatar img { padding-bottom: 5px; max-width: 100%; overflow-wrap: anywhere; - } br { @@ -1021,7 +887,6 @@ select { flex: 1; } - .text_pole::placeholder { color: rgb(139, 139, 139); } @@ -1032,30 +897,7 @@ select { white-space: nowrap; } -.margin-bot-10px, -.marginBot10 { - margin-bottom: 10px; -} -.marginTop10 { - margin-top: 10px; -} - -.marginBot5 { - margin-bottom: 5px; -} - -.marginTop5 { - margin-top: 5px; -} - -.marginTopBot5 { - margin: 5px 0; -} - -.margin5 { - margin: 5px; -} #description_textarea, #firstmessage_textarea { @@ -1275,6 +1117,7 @@ input[type="file"] { flex-direction: row; gap: 5px; justify-content: center; + align-items: center; } #rm_print_characters_block { @@ -1284,60 +1127,6 @@ input[type="file"] { height: 100%; } -body.charListGrid #rm_print_characters_block { - display: flex; - gap: 5px; - flex-wrap: wrap; - flex-direction: row; - justify-content: space-around; - align-content: flex-start; -} - -body.charListGrid #rm_print_characters_block .character_select { - width: 30%; - align-items: flex-start; - height: min-content; - flex-direction: column; - overflow: hidden; - max-width: 100px; -} - -body.charListGrid #rm_print_characters_block .character_select .ch_name, -body.charListGrid #rm_print_characters_block .group_select .ch_name { - width: 100%; - max-width: 100px; - text-align: center; - font-size: calc(var(--mainFontSize) * .8); -} - -body.charListGrid #rm_print_characters_block .character_select .character_name_block { - width: 100%; -} - -body.charListGrid #rm_print_characters_block .character_select .character_select_container { - width: 100%; - justify-content: center; - max-width: 100px; -} - -body.charListGrid #rm_print_characters_block .group_select { - width: 30%; - height: min-content; - align-items: center !important; - flex-direction: column; - overflow: hidden; - max-width: 100px; -} - -body.charListGrid #rm_print_characters_block .group_select .group_name_block { - width: 100%; -} - -body.charListGrid #rm_print_characters_block .ch_description, -body.charListGrid #rm_print_characters_block .tags_inline { - display: none; -} - #rm_ch_create_block { display: none; overflow-y: auto; @@ -1350,7 +1139,8 @@ body.charListGrid #rm_print_characters_block .tags_inline { overflow-y: auto; } -#floatingPrompt, #cfgConfig { +#floatingPrompt, +#cfgConfig { overflow-y: auto; max-width: 90svw; max-height: 90svh; @@ -1381,7 +1171,7 @@ body.charListGrid #rm_print_characters_block .tags_inline { #extension_floating_counter { font-weight: 600; - color: orange; + color: var(--SmartThemeQuoteColor); } .extension_token_counter { @@ -1463,11 +1253,15 @@ select option:not(:checked) { } .fav_on { - color: #c5b457 !important; + color: var(--golden) !important; } .world_set { - color: #4b9c00 !important; + color: var(--active) !important; +} + +#instruct_set_default.default { + color: var(--preferred) !important; } .displayBlock { @@ -1485,7 +1279,7 @@ select option:not(:checked) { #api_button:hover, #api_button_novel:hover, #api_button_textgenerationwebui:hover { - background-color: green; + background-color: var(--active); } .api-load-icon { @@ -1493,10 +1287,6 @@ select option:not(:checked) { display: none; } -.overflowYAuto { - overflow-y: auto; -} - #rm_characters_block { display: flex; overflow-y: auto; @@ -1581,11 +1371,6 @@ input[type=search]:focus::-webkit-search-cancel-button { padding: 5px; border-radius: 10px; cursor: pointer; - -} - -.heightMinContent { - height: min-content; } /*applies to char list and mes_text char display name*/ @@ -1605,11 +1390,6 @@ input[type=search]:focus::-webkit-search-cancel-button { font-style: italic; } -body.charListGrid #rm_print_characters_block .character_version, -body.charListGrid #rm_print_characters_block .ch_avatar_url { - display: none; -} - .character_select .avatar { align-self: center; } @@ -1623,18 +1403,6 @@ body.charListGrid #rm_print_characters_block .ch_avatar_url { margin-top: -5px; } -body.big-avatars .ch_description { - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - white-space: normal; - text-overflow: unset; -} - -.justifySpaceBetween { - justify-content: space-between; -} - .mes_block .ch_name { max-width: 100%; } @@ -1653,43 +1421,6 @@ body.big-avatars .ch_description { background-color: var(--white30a); } -.alignitemsflexstart { - align-items: flex-start !important; -} - -.alignItemsFlexEnd { - align-items: flex-end !important; -} - -.alignSelfStart { - align-self: start; -} - -.gap5px { - gap: 5px !important; -} - -.gap10px { - gap: 10px !important; -} - -.wide10pMinFit { - width: 10%; - min-width: fit-content; -} - -.wide100pLess70px { - width: calc(100% - 70px); -} - -.wideMax100px { - max-width: 100px; -} - -.widthUnset { - width: unset; -} - /*LEFT SIDE BG MENU*/ #logo_block { @@ -1775,14 +1506,6 @@ body.big-avatars .ch_description { left: 10px; } -.no-border { - border: none !important; -} - -.no-shadow { - box-shadow: none !important; -} - .add_bg_but { cursor: pointer; opacity: 0.1; @@ -1817,18 +1540,6 @@ body.big-avatars .ch_description { align-items: center; } -/* #avatar_div_div.avatar img { - height: 90%; - width: unset; - aspect-ratio: 1/1; -} - -body.big-avatars #avatar_div_div.avatar img { - height: 90%; - width: unset; - aspect-ratio: 2 / 3; -} - */ #user-settings-block h4, #user-settings-block h3 { margin: 5px 0; @@ -1838,13 +1549,11 @@ body.big-avatars #avatar_div_div.avatar img { justify-content: space-between; display: flex; flex-wrap: wrap; - /* margin-bottom: 4px; */ } - .ch_fav_icon { filter: drop-shadow(1px 1px 2px black); - color: #c5b457; + color: var(--golden); font-size: 14px; order: -1; margin-left: -75px; @@ -1856,7 +1565,13 @@ body.big-avatars #avatar_div_div.avatar img { .character_select.is_fav .avatar, .group_select.is_fav .avatar, .group_member.is_fav .avatar { - outline: 2px solid #c5b457; + outline: 2px solid var(--golden); +} + +.character_select.is_fav .ch_name, +.group_select.is_fav .ch_name, +.group_member.is_fav .ch_name { + color: var(--golden); } #fav_chara_wrap { @@ -1985,35 +1700,6 @@ grammarly-extension { align-items: center; } -#rm_info_block { - display: none; - width: 100%; - height: 80%; - justify-content: center; - flex-direction: column; - -} - -#rm_info_panel { - font-size: calc(var(--mainFontSize) + .5rem); - display: block; - text-align: center; -} - -#rm_info_button { - width: min-content; - margin: 0 auto; -} - -#rm_info_avatar { - display: flex; - column-gap: 10px; - width: fit-content; - text-align: center; - margin-left: auto; - margin-right: auto; -} - #delete_button, .redWarningBG { background-color: var(--crimson70a) !important; @@ -2068,12 +1754,6 @@ grammarly-extension { justify-content: space-between; } -.user_stats_button { - /* position: absolute; - right: 0; - bottom: 0; */ -} - .large_dialogue_popup { height: 90vh !important; height: 90svh !important; @@ -2087,11 +1767,6 @@ grammarly-extension { width: fit-content !important; } -.height100pSpaceEvenly { - align-content: space-evenly; - height: 100%; -} - #dialogue_popup_holder { display: flex; flex-direction: column; @@ -2182,10 +1857,6 @@ grammarly-extension { background-color: var(--white30a); } -.height32px { - height: 32px; -} - #dialogue_del_mes .menu_button { margin-left: 25px; @@ -2337,189 +2008,14 @@ grammarly-extension { cursor: grab; } -.world_info_select_block { - display: flex; - flex-direction: row; - align-items: baseline; - gap: 5px; -} - -.budget_cap_note { - flex-basis: 100%; - line-height: 0.1; -} - -#world_popup { - min-height: 100px; - min-width: 100px; - left: 0; - right: 0; - flex-direction: column; - z-index: 3010; - overflow-y: hidden; -} - -.WIEntryContentAndMemo { - width: 100% !important; - flex-wrap: nowrap !important; -} - -.WIEntryContentAndMemo .world_entry_thin_controls { - flex: 1; -} - -#world_popup_bottom_holder { - display: flex; - flex-flow: row; - justify-content: space-evenly; - align-items: center; -} - -#world_popup_bottom_holder div { - width: fit-content; - user-select: none; - opacity: 0.8; -} - -.world_popup_logo_block { - display: flex; - align-items: center; -} - -#world_popup_header { - display: flex; - flex-direction: row; - align-items: center; -} - -#world_popup_header h3 { - margin: 0; -} - -#form_rename_world, #form_rename_chat { display: flex; align-items: center; - + flex: 1; opacity: 0.8; gap: 5px; } -#form_rename_chat { - flex: 1; -} - -#form_rename_world input[type="submit"] { - cursor: pointer; -} - -#form_world_import { - display: none; -} - -#world_popup_header h5 { - display: inline-block; -} - -.world_popup_expander { - flex-grow: 1; -} - -#world_popup_entries_list { - flex-grow: 1; - overflow-y: auto; -} - -#world_popup_entries_list:empty { - width: 100%; - height: 100%; -} - -#world_popup_entries_list:empty::before { - content: 'No entries exist. Try creating one!'; - font-size: calc(var(--mainFontSize) + .5rem); - font-weight: bolder; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - opacity: 0.8; -} - -.world_entry_form_control { - display: flex; - flex-direction: column; -} - -.world_entry_thin_controls { - display: flex; - flex-direction: row; -} - -/* .world_entry_thin_controls>div { - flex: 1; -} */ - -.world_entry_form_control label h4 { - margin-bottom: 0; - margin-top: 0; -} - -.world_entry_form_control label h5 { - margin-top: 3px; - margin-bottom: 3px; -} - -.world_entry_form_control textarea { - height: auto; - margin-top: 0; - margin-bottom: 0; - min-height: 32px; -} - -.delete_entry_button { - height: min-content; -} - -.world_entry_form_control.world_entry_form_horizontal { - flex-direction: row; - align-items: center; - flex-wrap: wrap; -} - -.world_entry_form_control input[type=button] { - cursor: pointer; -} - -.world_entry_form_horizontal h5 { - margin: 0 1rem; -} - -.world_entry_form_control .checkbox { - align-items: center; - display: flex; - flex-direction: row; - column-gap: 10px; -} - -.world_entry_form_control .checkbox h4 { - margin: 0; - display: inline-block; -} - -.world_entry_form_radios label { - margin-left: 0; -} - -.world_entry_form_radios h4 { - display: inline; -} - -#world_popup h5 { - color: var(--grey70); -} - /* STLYES FOR THE CHAT MESSAGE DELETION CHECKBOXES */ /* ------------------------------------------------*/ @@ -2730,7 +2226,6 @@ input[type="range"]::-webkit-slider-thumb { transition: all 250ms; } - .note-link-span:hover { opacity: 1; } @@ -3043,12 +2538,6 @@ h5 { border: 1px solid var(--grey30); } -.TxtLrgBoldCenter { - text-align: center; - font-size: large; - font-weight: 600; -} - #export_div { cursor: pointer; } @@ -3177,199 +2666,6 @@ h5 { grid-template-columns: 340px auto; } -#tags_div { - min-width: 0; -} - -.tag_controls { - display: flex; - flex-direction: row; - gap: 5px; - align-items: center; -} - -.tag_view_item { - display: flex; - flex-direction: row; - align-items: baseline; - gap: 10px; - margin-bottom: 5px; -} - -.tag_view_name { - text-align: left; -} - -.tag_view_counter { - text-align: right; - flex: 1; -} - -.tag_delete { - padding-right: 0; - color: var(--SmartThemeBodyColor) !important; -} - -.tag { - border-radius: 5px; - border-style: solid; - border-width: 1px; - box-sizing: border-box; - color: var(--SmartThemeBodyColor); - background-color: var(--black30a); - border-color: var(--white50a); - padding: 0.1rem 0.2rem; - font-size: calc(var(--mainFontSize) - 5%); - display: flex; - flex-direction: row; - align-items: center; - position: relative; - gap: 10px; - width: fit-content; - min-width: 0; - text-shadow: none !important; -} - -.rm_tag_filter .tag:not(.actionable) { - display: none; -} - -.group_pagination { - display: flex; - justify-content: center; - align-items: center; -} - -#rm_group_chats_block .tag.filterByGroups { - display: none; -} - -.tag.actionable { - border-radius: 50%; - aspect-ratio: 1 / 1; - min-height: calc(var(--mainFontSize) * 2); - min-width: calc(var(--mainFontSize) * 2); - font-size: calc(var(--mainFontSize) * 1); - padding: 0; - display: flex; - justify-content: center; - align-items: center; -} - -.tagListHint { - align-self: center; - display: flex; - margin-right: 10px; -} - -.opacity1 { - opacity: 1 !important; -} - -.margin-right-10px { - margin-right: 10px; -} - -/* .tag.actionable::after { - font-size: calc(var(--mainFontSize) - 5%); - content: "|"; - position: absolute; - right: -12px; -} */ - - -.tag_remove { - cursor: pointer; -} - -.tags { - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: flex-start; - gap: 0.2rem; - align-items: flex-end; -} - -#tagList.tags { - margin: 5px 0; -} - -#tagList .tag { - opacity: 0.6; -} - -.tags.tags_inline { - opacity: 0.6; - column-gap: 0.2rem; - row-gap: 0.2rem; - justify-content: flex-start; - max-height: 66%; - overflow: hidden; -} - -.tag_name { - text-overflow: ellipsis; - overflow: hidden; - text-align: left; - white-space: nowrap; -} - -.tags_inline .tag { - font-size: calc(var(--mainFontSize) - 15%); - padding: 0 0.15rem; -} - -.rm_tag_controls { - display: flex; - column-gap: 10px; - flex-direction: row; - align-items: flex-start; - margin: 5px; -} - -.rm_tag_filter .tag { - cursor: pointer; - opacity: 0.6; - filter: brightness(0.8); -} - -.tags_view, -.open_alternate_greetings { - margin: 0; - aspect-ratio: 1 / 1; - height: 2rem; -} - -.tag.selected { - opacity: 1 !important; - filter: none !important; -} - -.tag.excluded { - opacity: 1 !important; - filter: saturate(0.4) !important; - border: 1px solid red; -} - -.tag.excluded:after { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - content: "\d7"; - font-size: calc(var(--mainFontSize) *3); - color: red; - line-height: calc(var(--mainFontSize)*1.3); - text-align: center; - text-shadow: 1px 1px 0px black, - -1px -1px 0px black, - -1px 1px 0px black, - 1px -1px 0px black; - opacity: 1; -} - #avatarCropWrap { margin: 10px auto; max-height: 90%; @@ -3424,221 +2720,6 @@ body .ui-widget-content li:hover { opacity: 1; } -/* GROUP CHATS */ - - -#rm_button_group_chats h2 { - margin-top: auto; - margin-bottom: auto; - color: rgb(188, 193, 200, 1); - border: 1px solid #333; - background-color: rgba(0, 0, 0, 0.3); - padding: 6px; - border-radius: 10px; -} - -#rm_group_chats_block { - display: none; - align-items: flex-start; - padding: 0 5px; - overflow-y: auto; -} - -#rm_group_chats_block h3, -#rm_group_chats_block h5 { - margin-top: 5px; - margin-bottom: 5px; -} - -#rm_group_buttons>div { - display: flex; - flex-direction: column; -} - -#rm_group_buttons .checkbox { - display: flex; -} - -#rm_group_buttons .checkbox h4 { - display: inline-block; -} - -#rm_group_buttons>input { - - cursor: pointer; - user-select: none; -} - -#rm_group_buttons>input:disabled { - filter: brightness(0.3); - cursor: unset; -} - -#rm_group_members, -#rm_group_add_members { - margin-top: 0.25rem; - margin-bottom: 0.5rem; - border: 1px solid grey; - border-radius: 10px; - background-color: var(--black30a); -} - -#rm_group_buttons_expander { - flex-grow: 1; -} - -#rm_group_delete { - color: rgb(190, 0, 0); -} - -#rm_group_members:empty { - width: 100%; -} - -#rm_group_members:empty::before { - content: 'Group is empty'; - - font-weight: bolder; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - opacity: 0.8; -} - -#rm_group_add_members:empty { - width: 100%; -} - -#rm_group_add_members_header { - display: flex; - flex-direction: row; - width: 100%; - column-gap: 10px; -} - -#rm_group_add_members_header input { - flex: 1; - width: 100%; -} - -#rm_group_add_members:empty::before { - content: 'No characters available'; - - font-weight: bolder; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - opacity: 0.8; -} - -.group_member_icon { - display: flex; - column-gap: 10px; - align-items: center; - justify-content: end; - flex-grow: 1; -} - -.group_member { - display: flex; - flex-direction: row; - align-items: center; - width: 100%; - padding: 5px; - border-radius: 10px; -} - -.group_member .group_member_name { - flex-grow: 1; - margin-left: 10px; - overflow: hidden; - text-overflow: ellipsis; - width: calc(100% - 110px); - display: flex; - gap: 5px; - height: 100%; - flex-direction: column; - justify-content: center; -} - -.group_member_icon .flex-container { - gap: 0px; -} - -#rm_group_members .right_menu_button, -#rm_group_add_members .right_menu_button { - padding: 0px; - height: 20px; - display: flex; - align-items: center; -} - -#rm_group_members .right_menu_button[data-action="speak"], -#rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="disable"] { - opacity: 0.4; - filter: brightness(0.5); - transition: all 0.2s ease-in-out; -} - -/* #rm_group_members .right_menu_button[data-action="speak"]:hover, */ -#rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="disable"]:hover { - opacity: inherit; - filter: drop-shadow(0px 0px 5px rgb(243, 166, 65)); -} - -#rm_group_members .group_member.disabled .right_menu_button[data-action="enable"] { - filter: drop-shadow(0px 0px 5px rgb(243, 166, 65)); -} - - -#rm_group_members .right_menu_button[data-action="speak"]:hover { - opacity: inherit; - filter: drop-shadow(0px 0px 5px rgb(153, 255, 153)); -} - -/* Rules for icon display */ -#rm_group_add_members .right_menu_button:not([data-action="add"], [data-action="view"]), -#rm_group_members .right_menu_button[data-action="add"], -#rm_group_members .group_member.disabled .right_menu_button[data-action="disable"], -#rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="enable"] { - display: none; -} - -.group_select { - display: flex; - flex-direction: row; - padding: 5px; - border-radius: 10px; - cursor: pointer; -} - -.group_select:hover { - background-color: var(--white30a); -} - -.group_select .avatar { - flex: 0; -} - -.group_select .group_icon { - width: 20px; - height: 20px; - margin: 0 10px; -} - -.group_select .group_fav_icon { - filter: drop-shadow(0px 0px 1px black); - color: #c5b457; - font-size: 12px; - order: -1; - margin-left: -18px; - margin-top: 3px; -} - .typing_indicator { position: sticky; bottom: 10px; @@ -3647,10 +2728,6 @@ body .ui-widget-content li:hover { text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor); } -.missing-avatar { - font-size: 36px; -} - .typing_indicator:after { display: inline-block; vertical-align: bottom; @@ -3659,8 +2736,8 @@ body .ui-widget-content li:hover { width: 0px; } -.group_member .avatar { - flex-shrink: 0; +.missing-avatar { + font-size: 36px; } @keyframes ellipsis { @@ -3681,158 +2758,8 @@ body .ui-widget-content li:hover { } } -.avatar_collage { - border-radius: 50%; - position: relative; - overflow: hidden; -} - -body:not(.big-avatars) .avatar_collage { - min-width: 50px; - aspect-ratio: 1 / 1; -} - -body.big-avatars .avatar_collage { - min-width: 60px; - max-width: 60px; - aspect-ratio: 2 / 3; -} - - -.avatar_collage img { - position: absolute; - overflow: hidden; -} - -body:not(.big-avatars) .avatar { - border-radius: 50%; -} - -body:not(.big-avatars) .avatar_collage img { - border-radius: 0% !important; -} - -.collage_2 .img_1 { - left: 0; - top: 0; - max-width: 50%; - max-height: 100%; - width: 50%; - height: 100%; -} - -.collage_2 .img_2 { - left: 50%; - top: 0; - max-width: 50%; - max-height: 100%; - width: 50%; - height: 100%; -} - -.collage_3 .img_1 { - left: 0; - top: 0; - max-width: 50%; - max-height: 50%; - width: 50%; - height: 50%; -} - -.collage_3 .img_2 { - left: 50%; - top: 0; - max-width: 50%; - max-height: 50%; - width: 50%; - height: 50%; -} - -.collage_3 .img_3 { - left: 0; - top: 50%; - max-width: 100%; - max-height: 50%; - width: 100%; - height: 50%; -} - -.collage_4 .img_1 { - left: 0; - top: 0; - max-width: 50%; - max-height: 50%; - width: 50%; - height: 50%; -} - -.collage_4 .img_2 { - left: 50%; - top: 0; - max-width: 50%; - max-height: 50%; - width: 50%; - height: 50%; -} - -.collage_4 .img_3 { - left: 0; - top: 50%; - max-width: 50%; - max-height: 50%; - width: 50%; - height: 50%; -} - -.collage_4 .img_4 { - left: 50%; - top: 50%; - max-width: 50%; - max-height: 50%; - width: 50%; - height: 50%; -} - -/* border radius for big avatars collages */ - -body.big-avatars .collage_2 .img_1 { - border-radius: 10px 0 0 10px !important; -} - -body.big-avatars .collage_2 .img_2 { - border-radius: 0 10px 10px 0 !important; -} - -body.big-avatars .collage_3 .img_1 { - border-radius: 10px 0 0 0 !important; -} - -body.big-avatars .collage_3 .img_2 { - border-radius: 0 10px 0 0 !important; -} - -body.big-avatars .collage_3 .img_3 { - border-radius: 0 0 10px 10px !important; -} - -body.big-avatars .collage_4 .img_1 { - border-radius: 10px 0 0 0 !important; -} - -body.big-avatars .collage_4 .img_2 { - border-radius: 0 10px 0 0 !important; -} - -body.big-avatars .collage_4 .img_3 { - border-radius: 0 0 0 10px !important; -} - -body.big-avatars .collage_4 .img_4 { - border-radius: 0 0 10px 0 !important; -} - span.warning { - color: rgba(255, 0, 0, 0.5); + color: var(--warning); font-weight: bolder; } @@ -3849,7 +2776,6 @@ span.warning { flex: 1; } - #talkativeness_div input[type="range"] { width: 100%; } @@ -3995,13 +2921,6 @@ a { display: none; } -.hiddenByTag, -.hiddenByFav, -.hiddenByGroup, -.hiddenBySearch { - display: none !important; -} - /* Message images */ .mes .mes_img_container { max-width: 100%; @@ -4041,12 +2960,6 @@ a { filter: brightness(150%); } -/* -.mes_img_container:hover .mes_img { - opacity: 0.9; -} -*/ - .mes_img_container:hover .mes_img_controls { display: flex; } @@ -4066,115 +2979,12 @@ a { max-width: 100% !important; } -/* Extensions */ -#extensions_url { - display: block; -} - -#extensions_status { - /* margin-bottom: 10px; */ - font-weight: 700; -} - -.extensions_block input[type="submit"]:hover { - background-color: green; -} - -.extensions_block input[type="checkbox"] { - margin-left: 10px; - margin-right: 10px; -} - -label[for="extensions_autoconnect"] { - display: flex; - align-items: center; - margin: 0 !important; -} - -.extensions_url_block { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} - -.extensions_url_block h4 { - display: inline; -} - -.extensions_block { - clear: both; - padding: 0.05px; -} - -.extensions_info { - text-align: left; - margin: 0 1em; -} - -.extensions_info h3 { - margin-bottom: 0.5em; -} - -.extensions_info h4 { - margin-bottom: 0.5em; -} - -.extensions_info p { - margin-bottom: 0.5em; - margin-left: 1em; -} - -.extensions_info .disabled { - color: lightgray; -} - -.extensions_info .toggle_enable { - color: lightgreen; -} - -.extensions_info .toggle_disable { - color: rgb(238, 144, 144); -} - -.extensions_info .extension_enabled { - color: green; -} - -.extensions_info .extension_disabled { - color: lightgray; -} - -.extensions_info .extension_missing { - color: gray; -} - -input.extension_missing[type="checkbox"] { - opacity: 0.5; -} - -#extensions_list .disabled { - text-decoration: line-through; - color: lightgray; -} - -.update-button { - margin-right: 10px; - display: inline-flex; -} - /* Align the content of this span to the right */ .delete-button { margin-right: 10px; display: inline-flex; - } -/* possible place for WI Entry header styling */ -/* .world_entry_form .inline-drawer-header { - background-color: var(--SmartThemeShadowColor); -} */ - #extensions_settings .inline-drawer-toggle.inline-drawer-header, #extensions_settings2 .inline-drawer-toggle.inline-drawer-header { background-image: linear-gradient(348deg, var(--white30a)2%, var(--grey30a)10%, var(--black70a)95%, var(--SmartThemeQuoteColor)100%); @@ -4190,26 +3000,6 @@ input.extension_missing[type="checkbox"] { filter: brightness(150%); } -.success { - color: green; -} - -.failure { - color: red; -} - -.optional { - color: lightgray; -} - -.monospace { - font-family: monospace; -} - -.expander { - flex-grow: 1; -} - .menu_button_icon { display: flex; align-items: center; @@ -4287,12 +3077,6 @@ input.extension_missing[type="checkbox"] { filter: brightness(75%); } -.redOverlayGlow { - color: #800; - opacity: 0.8 !important; - text-shadow: none !important; -} - .inline-drawer-header { display: flex; justify-content: space-between; @@ -4370,185 +3154,19 @@ input.extension_missing[type="checkbox"] { } .fillLeft .scrollableInner { - padding: 0.5em 1em 0.5em 0.5em -} - -.width100p { - width: 100%; + padding: 0.5em 1em 0.5em 0.5em } .drawer-content select { width: 100%; } -toolcool-color-picker { - /* margin-right: 10px; */ -} - .settingsSectionWrap { border: 1px solid var(--white30a); border-radius: 10px; padding: 5px; } -.flex { - display: flex; -} - -.flex-container { - display: flex; - gap: 5px; - flex-wrap: wrap; -} - -.flexNoGap { - gap: unset !important; -} - -.flexGrow { - flex-grow: 1; -} - -.flexnowrap { - flex-wrap: nowrap; -} - -.alignitemscenter { - align-items: center; -} - -.alignitemsstart { - align-items: start; -} - -.overflow-hidden { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.maxWidth200px { - max-width: 200px; -} - -.alignContentFlexStart { - align-content: flex-start; -} - -.overflowHidden { - overflow: hidden; -} - -.padding5 { - padding: 5px; -} - -.padding10 { - padding: 10px; -} - -.margin0 { - margin: 0; -} - -.margin0auto { - margin: 0 auto; -} - -.margin-r5 { - margin-right: 5px; -} - -.flex1 { - flex: 1; -} - -.flex2 { - flex: 2 !important; -} - -.flexFlowColumn { - flex-flow: column; -} - -.wideMinContent { - width: min-content; -} - -.flexWide50p { - flex: 50%; -} - -.wide50p { - width: 50% !important; -} - -.wide25p { - width: 25%; -} - -.wide30p { - width: 30% !important; -} - -.justifyLeft { - text-align: start; - justify-content: left; - margin-left: unset; -} - -.justifyCenter { - justify-content: center; - margin: 0 auto; -} - -.justifyContentSpaceAround { - justify-content: space-around; -} - -.justifyContentFlexStart { - justify-content: flex-start; -} - -.justifyContentFlexEnd { - justify-content: flex-end; -} - -.spaceEvenly { - justify-content: space-evenly; -} - -.spaceBetween { - justify-content: space-between; -} - -.widthNatural { - width: unset !important; - min-width: unset !important; - max-width: unset !important; -} - -.widthFreeExpand { - width: -webkit-fill-available; - width: -moz-available; -} - -.wide100p { - width: 100%; -} - -.wide50p { - width: 50%; -} - -.wide50px { - width: 50px; -} - -.indent20p { - margin-left: 20px; -} - .wi-enter-footer-text { font-size: calc(var(--mainFontSize) * 0.8); color: var(--SmartThemeBodyColor); @@ -4632,14 +3250,6 @@ toolcool-color-picker { min-height: 2.5rem; } -/*used to fix smallness of certain FontAwesome glyph which break button squareness*/ -/*currently used on: CharList Import*/ - -.faSmallFontSquareFix { - font-size: calc(var(--mainFontSize) *1.25); - width: calc(var(--mainFontSize) *1.95); -} - .openai_logit_bias_preset_form { display: flex; flex-direction: row; @@ -4681,6 +3291,7 @@ toolcool-color-picker { color: var(--SmartThemeQuoteColor); font-weight: bold; font-style: italic; + font-size: 0.8em; } .openai_restorable .right_menu_button img { @@ -4709,8 +3320,8 @@ toolcool-color-picker { } .reverse_proxy_warning { - color: red; - background-color: black; + color: var(--warning); + background-color: var(--black70a); text-shadow: none !important; margin-top: 12px !important; border-radius: 5px; @@ -4719,7 +3330,7 @@ toolcool-color-picker { } .neutral_warning { - color: rgba(225, 0, 0, 0.9); + color: var(--warning); font-weight: 800; } @@ -4739,179 +3350,6 @@ toolcool-color-picker { display: none; } -.textarea_compact { - font-size: calc(var(--mainFontSize) * 0.9); - line-height: 1.2; -} - -/*bubble chat style*/ - -body.bubblechat .mes { - padding: 10px; - border-radius: 10px; - background-color: var(--SmartThemeBotMesBlurTintColor); - margin-bottom: 5px; - border: 1px solid var(--white30a); -} - -body.bubblechat .mes[is_user="true"] { - background-color: var(--SmartThemeUserMesBlurTintColor); -} - -/* body.w1000px #sheld { - max-width: 1000px !important; -}*/ - - -/* Document Style */ - -body.documentstyle #chat .mes:not(.last_mes) { - padding: 0 10px; -} - -body.documentstyle .last_mes { - padding-top: 0; -} - -body.documentstyle #chat .mes .mes_text { - padding: 0; -} - -body.documentstyle #chat .mes .mes_block { - margin-right: 30px; -} - -body.documentstyle #chat .mes .mes_text { - margin-left: 20px; -} - -body.documentstyle #chat .last_mes .mes_text { - margin-left: 20px; - min-height: 70px; -} - -body.documentstyle #chat .last_mes:has(> .del_checkbox[style*="display: block"]) .mes_text { - margin-left: 0px; -} - -body.documentstyle #chat .last_mes .swipe_left { - left: 5px; -} - -body.documentstyle #chat .mes .mesAvatarWrapper, -body.documentstyle #chat .mes .mes_block .ch_name .name_text, -body.documentstyle #chat .mes .mes_block .ch_name .timestamp, -body.documentstyle .mes:not(.last_mes) .ch_name .mes_buttons { - display: none !important; -} - - - - -/*FastUI blur removal*/ - -body.no-blur * { - backdrop-filter: unset !important; -} - -body.no-blur #send_form.no-connection { - background-color: rgba(100, 0, 0, 0.9) !important; -} - -body.no-blur #bg1, -body.no-blur #bg_custom { - filter: unset; - -} - -body:not(.bubblechat).no-blur #chat, -body.no-blur #top-bar, -body.no-blur #send_form { - background-color: var(--SmartThemeBlurTintColor) !important; -} - -body.no-blur #options, -body.no-blur .ui-widget-content, -body.no-blur #floatingPrompt, -body.no-blur #extensionsMenu, -body.no-blur .list-group, -body.no-blur #character_popup, -body.no-blur #world_popup, -body.no-blur #dialogue_popup, -body.no-blur #select_chat_popup, -body.no-blur .drawer-content, -body.no-blur .select2-results__options { - background-color: black !important; -} - -/* wAIfu mode*/ - -body.waifuMode #top-bar { - border-radius: 0 0 20px 20px; - border: 1px solid var(--grey30a); -} - -body.waifuMode #sheld { - height: 40vh; - height: 40svh; - top: calc(100% - 40vh); - bottom: 0; -} - -body.waifuMode #chat { - border-top: 1px solid var(--grey30a); - border-radius: 20px 20px 0 0; -} - -body.waifuMode #expression-wrapper { - justify-content: center; -} - -body.waifuMode .expression-holder { - max-height: 90vh; - max-width: 90vw; - height: 90vh; - width: fit-content; - bottom: 0; - filter: drop-shadow(2px 2px 2px #51515199); - z-index: 2; - margin: 0 auto; - left: 0; - right: 0; -} - -/* movingUI*/ - -body.movingUI .drag-grabber { - display: inline; -} - -body.movingUI #sheld, -body.movingUI .drawer-content, -body.movingUI #expression-holder, -body.movingUI .zoomed_avatar, -body.movingUI #floatingPrompt, -body.movingUI #groupMemberListPopout { - resize: both; -} - -#expression-image.default, -#expression-holder:has(.default) { - height: 120px; - margin-top: 0; - top: 50px; -} - -/*No Text Shadows Mode*/ - -body.noShadows * { - text-shadow: none !important; -} - -.katex-html { - display: none; -} - #rm_group_add_members a, #rm_group_members a { color: var(--SmartThemeBodyColor); @@ -4926,7 +3364,7 @@ body.noShadows * { width: calc((100vw - var(--sheldWidth)) /2); position: absolute; padding: 0; - filter: drop-shadow(2px 2px 2px #51515199); + filter: drop-shadow(2px 2px 2px var(--grey7070a)); z-index: 29; overflow: hidden; display: none; @@ -4953,38 +3391,12 @@ body.noShadows * { #groupMemberListPopout #rm_group_members { /* background-color: var(--SmartThemeBlurTintColor); */ - margin: 0; padding: 0; padding-top: 20px; } -.hoverglow:hover { - opacity: 1 !important; - cursor: pointer; - -} - -body.waifuMode .zoomed_avatar { - min-width: 100px; - min-height: 100px; - max-height: 90vh; - max-width: 90vh; - width: calc((100vw - var(--sheldWidth)) /2); - position: absolute; - padding: 0; - filter: drop-shadow(2px 2px 2px #51515199); - z-index: 29; - overflow: hidden; - display: none; - left: 0; - right: 0; - margin: 0 auto; - top: 50px; - aspect-ratio: 2 / 3; -} - .zoomed_avatar img { /* border: 1px solid var(--white50a); */ border-radius: 20px; @@ -4994,627 +3406,15 @@ body.waifuMode .zoomed_avatar { object-fit: cover; } -.debug-red { - border: 1px solid red !important; -} - -.debug-yellow { - border: 1px solid yellow !important; -} - -.debug-green { - border: 1px solid green !important; -} - -.debug-blue { - border: 1px solid blue !important; -} - -.debug-purple { - border: 1px solid purple !important; -} - -.fontsize80p { - font-size: calc(var(--mainFontSize) * 0.8) !important; -} - -.fontsize60p { - font-size: calc(var(--mainFontSize) * 0.6) !important; -} - -.paddingTopBot5 { - padding: 5px 0; -} - -.paddingLeftRight5 { - padding: 0 5px; -} - -.heightFitContent { - height: fit-content; -} - -.widthFitContent { - width: fit-content; -} - -.flexGap5 { - gap: 5px; -} - -.flexGap10 { - gap: 10px; -} - .timestamp { font-size: calc(var(--mainFontSize) * 0.7); font-weight: 400; } - -/* ---------- @media queries must always go at the bottom ------------*/ -/*will apply to anything 1000px or less. this catches ipads, horizontal phones, and vertical phones)*/ - -@media screen and (max-width: 1000px) { - - .bg_button { - font-size: 15px; - } - - #extensions_settings, - #extensions_settings2 { - width: 100% !important; - min-width: 100% !important; - } - - body:not(.waifuMode) .zoomed_avatar { - min-width: 100px; - min-height: 100px; - position: absolute; - padding: 0; - filter: drop-shadow(2px 2px 2px #51515199); - z-index: 30; - overflow: hidden; - left: 0; - right: 0; - margin: 0 auto; - top: 50px; - aspect-ratio: 2 / 3; - width: fit-content; - max-height: calc(60vh - 60px); - max-height: calc(60svh - 60px); - max-width: 90vw; - max-width: 90svw; - } - - .world_entry_thin_controls, - #persona-management-block, - #character_popup .flex-container { - flex-direction: column; - } - - #WIMultiSelector { - align-self: normal; - } - - .WIEntryContentAndMemo { - flex-flow: column; - } - - .WIEntryContentAndMemo .world_entry_thin_controls { - width: 100%; - } - - .world_entry_form_control.world_entry_form_horizontal { - /* flex-direction: column; */ - align-items: flex-start; - row-gap: 0.5rem; - } - - .world_entry_form_control.world_entry_form_horizontal .world_popup_expander { - display: none; - } - - /* #world_popup_header { - flex-direction: column; - align-items: flex-start; - } */ - - #world_popup_header .world_popup_expander { - display: none; - } - - body { - touch-action: none; - overflow: hidden; - position: fixed; - - } - - .drawer-content { - min-width: unset; - width: 100%; - max-height: calc(100vh - 45px); - max-height: calc(100svh - 45px); - position: fixed; - left: 0; - top: 5px; - border: 1px solid var(--grey30); - } - - #select_chat_popup { - align-items: start; - height: min-content; - align-content: start; - max-width: unset; - } - - #top-settings-holder, - #top-bar { - position: fixed; - padding-top: 8px; - width: 100vw; - width: 100svw; - } - - #bg1, - #bg_custom { - height: 100vh !important; - height: 100svh !important; - width: 100vw !important; - width: 100svw !important; - background-position: center; - - } - - - #sheld, - #character_popup, - .drawer-content - - /* , - #world_popup */ - { - max-height: calc(100vh - 40px); - max-height: calc(100svh - 40px); - width: 100% !important; - margin: 0 auto; - max-width: 100%; - left: 0 !important; - resize: none !important; - top: 40px; - } - - .wi-settings { - flex-direction: column; - } - - #character_popup, - #world_popup { - overflow-y: auto; - } - - #character_popup, - #send_form { - border: 1px solid var(--grey30); - backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); - max-width: 100dvw; - } - - #chat { - border-left: 1px solid var(--grey30); - border-right: 1px solid var(--grey30); - border-bottom: 1px solid var(--grey30); - align-items: start; - align-content: start; - overflow-y: auto; - overflow-x: hidden - } - - .mes_buttons { - font-size: calc(var(--mainFontSize)*1.2) - } - - ; - - .drag-grabber, - .pull-tab { - display: none !important; - - } - - #showRawPrompt, - #groupCurrentMemberPopoutButton { - display: none; - } - - .mes-text { - padding-right: 25px; - } - - #right-nav-panel, - #left-nav-panel, - #floatingPrompt, - #cfgConfig { - height: calc(100vh - 45px); - height: calc(100svh - 45px); - min-width: 100% !important; - width: 100% !important; - max-width: 100% !important; - overflow-y: hidden; - border-left: 1px solid var(--grey30); - border-right: 1px solid var(--grey30); - border-bottom: 1px solid var(--grey30); - border-radius: 0 0 20px 20px; - top: 40px !important; - left: 0 !important; - backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); - } - - #floatingPrompt, - #cfgConfig { - height: min-content; - } - - #right-nav-panel h4 { - margin: 5px auto; - } - - #result_info { - font-size: calc(var(--mainFontSize) - .1rem); - } - - /* .avatar_div { - margin-top: 5px; - } */ - - #character_popup { - width: 100%; - border-radius: 0 0 20px 20px; - margin-top: 0px; - height: calc(100% - 40px); - } - - .drawer25pWidth { - flex-basis: max(calc(100% / 4 - 10px), 190px); - } - - .drawer33pWidth { - flex-basis: max(calc(100% / 3 - 10px), 190px); - } - - .expression-holder { - display: none; - } - - body.waifuMode #sheld { - height: 40vh; - height: 40svh; - top: 60vh; - top: 60svh; - bottom: 0 !important; - } - - body.waifuMode .expression-holder { - display: inline; - - height: 100vh; - width: max-content; - margin: 0 auto; - position: absolute; - left: 0; - right: 0; - filter: drop-shadow(2px 2px 2px #51515199); - z-index: 1; - } - - body.waifuMode img.expression { - object-fit: cover; - } - - body.waifuMode .zoomed_avatar { - width: fit-content; - max-height: calc(60vh - 60px); - max-height: calc(60svh - 60px); - max-width: 90vw; - max-width: 90svw; - } - - .scrollableInner { - overflow-y: auto; - overflow-x: hidden; - max-height: calc(100vh - 90px); - max-height: calc(100svh - 90px); - } - - .horde_multiple_hint { - display: none; - } - - #bg_menu_content { - width: unset; - } -} - .lastInContext { border-top: 3px dotted var(--SmartThemeQuoteColor) !important; } -@media screen and (max-width: 1000px) and (orientation: landscape) { - body.waifuMode img.expression { - object-fit: contain; - } - - .tag.excluded:after { - top: unset; - bottom: unset; - } - - body:not(.waifuMode) .zoomed_avatar { - - width: fit-content; - max-height: calc(60vh - 60px); - max-height: calc(60svh - 60px); - max-width: 90vw; - max-width: 90svw; - } - - -} - -@media screen and (max-width: 450px) { - - body:not(.waifuMode) .zoomed_avatar { - min-width: 100px; - min-height: 100px; - max-height: 50vh; - max-width: 50vh; - width: 50vw; - position: absolute; - padding: 0; - filter: drop-shadow(2px 2px 2px #51515199); - z-index: 30; - overflow: hidden; - display: none; - left: 0; - right: 0; - margin: 0 auto; - top: 50px; - aspect-ratio: 2 / 3; - } - - .drawer25pWidth { - flex-basis: max(calc(100% / 2 - 10px), 180px); - } - - .drawer33pWidth { - flex-basis: max(calc(100% / 2 - 10px), 180px); - } - - .BGSampleTitle { - display: none; - } - - .tag.excluded:after { - top: unset; - bottom: unset; - } -} - - -/*this part only only applies to iOS devices*/ - -@supports (-webkit-touch-callout: none) { - body { - margin: 0 auto; - } - - #top-bar { - width: 100vw; - } - - #sheld { - margin: unset; - padding: unset; - width: unset; - height: unset; - min-width: unset; - max-width: unset; - min-height: unset; - max-height: unset; - width: 100vw; - width: 100svw; - height: calc(100vh - 40px); - height: calc(100svh - 40px); - padding-right: max(env(safe-area-inset-right), 0px); - padding-left: max(env(safe-area-inset-left), 0px); - padding-bottom: 0; - } - - body.PWA #sheld { - padding-right: max(env(safe-area-inset-right), 2px); - padding-left: max(env(safe-area-inset-left), 2px); - padding-bottom: max(env(safe-area-inset-bottom), 15px); - - } - - #character_popup, - #world_popup, - #left-nav-panel, - #right-nav-panel, - .drawer-content { - width: unset; - height: unset; - min-width: unset; - max-width: unset; - min-height: unset; - max-height: unset; - backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); - left: 0; - right: 0; - top: 0; - margin: 0 auto; - height: calc(100vh - 70px); - height: calc(100svh - 70px); - width: calc(100% - 5px); - max-height: calc(100vh - 70px); - max-height: calc(100svh - 70px); - max-width: calc(100% - 5px); - - } - - #character_popup, - #world_popup, - .drawer-content { - margin-top: 40px; - } - - .scrollableInner { - overflow-y: auto; - overflow-x: hidden; - } - - #horde_model { - height: unset; - } -} - - -/* Customize the Select2 container */ -.select2-container { - color: var(--SmartThemeBodyColor); -} - -/* Customize the dropdown */ -.select2-dropdown { - background-color: var(--SmartThemeBlurTintColor); - border: 1px solid var(--white30a) !important; - border-radius: 10px; - box-shadow: 0 0 5px black; - text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor); - backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)*2)); - color: var(--SmartThemeBodyColor); - z-index: 40000; -} - -.select2-selection__clear { - color: var(--SmartThemeBodyColor); -} - -.select2-container .select2-selection--multiple .select2-selection__choice__remove { - padding: revert; - border-right: 1px solid var(--white30a); - font-size: 1.1em; - -} - -.select2-container .select2-selection--multiple .select2-selection__choice__display { - padding-left: 5px; -} - -/* Customize the search input */ -.select2-search__field { - background-color: var(--black30a); - color: var(--SmartThemeBodyColor); - border: 1px solid var(--white30a); - border-radius: 7px; - font-family: "Noto Sans", "Noto Color Emoji", sans-serif; - padding: 3px 5px; -} - -/* Customize the selected option */ -.select2-selection--single { - border: 1px solid var(--SmartThemeShadowColor); - border-radius: 4px; - background-color: var(--SmartThemeBlurTintColor); -} - -/* Customize the selected option text */ -.select2-selection__rendered { - color: var(--SmartThemeBodyColor); -} - -/* Customize the option list item */ -.select2-results__option { - color: var(--SmartThemeBodyColor); - background-color: var(--SmartThemeBodyColor); -} - -.select2-container .select2-selection--multiple { - background-color: var(--black30a); - color: var(--SmartThemeBodyColor); - border: 1px solid var(--white30a); - border-radius: 7px; - font-family: "Noto Sans", "Noto Color Emoji", sans-serif; - padding: 3px 5px; -} - -.select2-container.select2-container--focus .select2-selection--multiple { - border: 1px solid var(--white30a); -} - -.select2-container .select2-selection--multiple .select2-selection__choice { - border-radius: 5px; - border-style: solid; - border-width: 1px; - box-sizing: border-box; - color: var(--SmartThemeBodyColor); - background-color: var(--black30a); - border-color: var(--white30a); - font-size: calc(var(--mainFontSize) - 5%); - text-shadow: none !important; -} - -.select2-results .select2-results__option--selectable { - background-color: unset; - color: var(--SmartThemeBodyColor); - opacity: 0.5; - transition: opacity 200ms ease-in-out; - position: relative; -} - -/* Customize the hovered option list item */ -.select2-results .select2-results__option--highlighted.select2-results__option--selectable { - color: var(--SmartThemeBodyColor); - background-color: unset; - opacity: 1; -} - -/* Customize the option list item */ -.select2-results__option { - padding-left: 30px; - /* Add some padding to make room for the checkbox */ -} - -/* Add the custom checkbox */ -.select2-results__option:before { - content: ''; - display: inline-block; - position: absolute; - left: 6px; - top: 50%; - margin-top: -7px; - width: 14px; - height: 14px; - border: 1px solid var(--white30a); - background-color: var(--SmartThemeBlurTintColor); - border-radius: 2px; -} - -.select2-container .select2-selection--multiple .select2-selection__choice__remove { - color: var(--SmartThemeBodyColor); -} - -/* Add the custom checkbox checkmark */ -.select2-results__option--selected.select2-results__option:before { - content: '\2713'; - font-weight: bold; - color: var(--SmartThemeBodyColor); - background-color: var(--SmartThemeBlurTintColor); - text-align: center; - line-height: 14px; -} - .icon-svg { fill: currentColor; /* Takes on the color of the surrounding text */ @@ -5646,6 +3446,7 @@ body.waifuMode .zoomed_avatar { justify-content: center; align-items: center; gap: 5px; + user-select: none; } .paginationjs-size-changer select { @@ -5684,3 +3485,19 @@ body.waifuMode .zoomed_avatar { font-size: calc(var(--mainFontSize) * .8); font-weight: bold; } + +#select_chat_search { + background-color: transparent; + border: none; + outline: none; + color: var(--SmartThemeBodyColor); + display: inline-block; + /* Change display to inline-block */ + vertical-align: middle; + /* Align to middle if there's a height discrepancy */ + width: 200px; + font-size: 16px; + z-index: 10; + margin-left: 10px; + /* Give some space between the button and search box */ +} diff --git a/server.js b/server.js index 0af4efd4c..7b74cf7aa 100644 --- a/server.js +++ b/server.js @@ -288,6 +288,7 @@ function humanizedISO8601DateTime() { var is_colab = process.env.colaburl !== undefined; var charactersPath = 'public/characters/'; var chatsPath = 'public/chats/'; +const UPLOADS_PATH = './uploads'; const AVATAR_WIDTH = 400; const AVATAR_HEIGHT = 600; const jsonParser = express.json({ limit: '100mb' }); @@ -296,6 +297,8 @@ const baseRequestArgs = { headers: { "Content-Type": "application/json" } }; const directories = { worlds: 'public/worlds/', avatars: 'public/User Avatars', + images: 'public/img/', + userImages: 'public/user/images/', groups: 'public/groups/', groupChats: 'public/group chats', chats: 'public/chats/', @@ -427,7 +430,7 @@ app.use('/characters', (req, res) => { res.send(data); }); }); -app.use(multer({ dest: "uploads", limits: { fieldSize: 10 * 1024 * 1024 } }).single("avatar")); +app.use(multer({ dest: UPLOADS_PATH, limits: { fieldSize: 10 * 1024 * 1024 } }).single("avatar")); app.get("/", function (request, response) { response.sendFile(process.cwd() + "/public/index.html"); }); @@ -598,7 +601,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r }); async function* readWebsocket() { - const streamingUrl = request.header('X-Streaming-URL'); + const streamingUrl = request.header('X-Streaming-URL').replace("localhost", "127.0.0.1"); const websocket = new WebSocket(streamingUrl); websocket.on('open', async function () { @@ -618,7 +621,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r websocket.close(); return; } - + let rawMessage = null; try { // This lunacy is because the websocket can fail to connect AFTER we're awaiting 'message'... so 'message' never triggers. @@ -632,7 +635,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r resolve(data, isBinary); }); }); - } catch(err) { + } catch (err) { console.error("Socket error:", err); websocket.close(); yield "[SillyTavern] Streaming failed:\n" + err; @@ -1005,7 +1008,7 @@ function charaFormatData(data) { return char; } -app.post("/createcharacter", urlencodedParser, function (request, response) { +app.post("/createcharacter", urlencodedParser, async function (request, response) { if (!request.body) return response.sendStatus(400); request.body.ch_name = sanitize(request.body.ch_name); @@ -1022,8 +1025,9 @@ app.post("/createcharacter", urlencodedParser, function (request, response) { charaWrite(defaultAvatar, char, internalName, response, avatarName); } else { const crop = tryParse(request.query.crop); - const uploadPath = path.join("./uploads/", request.file.filename); - charaWrite(uploadPath, char, internalName, response, avatarName, crop); + const uploadPath = path.join(UPLOADS_PATH, request.file.filename); + await charaWrite(uploadPath, char, internalName, response, avatarName, crop); + fs.unlinkSync(uploadPath); } }); @@ -1120,9 +1124,10 @@ app.post("/editcharacter", urlencodedParser, async function (request, response) await charaWrite(avatarPath, char, target_img, response, 'Character saved'); } else { const crop = tryParse(request.query.crop); - const newAvatarPath = path.join("./uploads/", request.file.filename); + const newAvatarPath = path.join(UPLOADS_PATH, request.file.filename); invalidateThumbnail('avatar', request.body.avatar_url); await charaWrite(newAvatarPath, char, target_img, response, 'Character saved', crop); + fs.unlinkSync(newAvatarPath); } } catch { @@ -1373,7 +1378,20 @@ app.post("/getcharacters", jsonParser, function (request, response) { }); }); +app.post("/getonecharacter", jsonParser, async function (request, response) { + if (!request.body) return response.sendStatus(400); + const item = request.body.avatar_url; + const filePath = path.join(charactersPath, item); + if (!fs.existsSync(filePath)) { + return response.sendStatus(404); + } + + characters = {}; + await processCharacter(item, 0); + + return response.send(characters[0]); +}); /** * Handle a POST request to get the stats object @@ -1531,13 +1549,14 @@ app.post("/downloadbackground", urlencodedParser, function (request, response) { response_dw_bg = response; if (!request.body || !request.file) return response.sendStatus(400); - const img_path = path.join("uploads/", request.file.filename); + const img_path = path.join(UPLOADS_PATH, request.file.filename); const filename = request.file.originalname; try { fs.copyFileSync(img_path, path.join('public/backgrounds/', filename)); invalidateThumbnail('bg', filename); response_dw_bg.send(filename); + fs.unlinkSync(img_path); } catch (err) { console.error(err); response_dw_bg.sendStatus(500); @@ -2052,13 +2071,15 @@ app.post("/importcharacter", urlencodedParser, async function (request, response let png_name = ''; let filedata = request.file; - let uploadPath = path.join('./uploads', filedata.filename); + let uploadPath = path.join(UPLOADS_PATH, filedata.filename); var format = request.body.file_type; const defaultAvatarPath = './public/img/ai4.png'; //console.log(format); if (filedata) { if (format == 'json') { fs.readFile(uploadPath, 'utf8', async (err, data) => { + fs.unlinkSync(uploadPath); + if (err) { console.log(err); response.send({ error: true }); @@ -2139,8 +2160,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response if (format == 'webp') { try { - let convertedPath = path.join('./uploads', path.basename(uploadPath, ".webp") + ".png") + let convertedPath = path.join(UPLOADS_PATH, path.basename(uploadPath, ".webp") + ".png") await webp.dwebp(uploadPath, convertedPath, "-o"); + fs.unlinkSync(uploadPath); uploadPath = convertedPath; } catch { @@ -2155,7 +2177,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response unsetFavFlag(jsonData); jsonData = readFromV2(jsonData); let char = JSON.stringify(jsonData); - charaWrite(uploadPath, char, png_name, response, { file_name: png_name }); + await charaWrite(uploadPath, char, png_name, response, { file_name: png_name }); + fs.unlinkSync(uploadPath); } else if (jsonData.name !== undefined) { console.log('Found a v1 character file.'); @@ -2181,6 +2204,7 @@ app.post("/importcharacter", urlencodedParser, async function (request, response char = convertToV2(char); char = JSON.stringify(char); await charaWrite(uploadPath, char, png_name, response, { file_name: png_name }); + fs.unlinkSync(uploadPath); } else { console.log('Unknown character card format'); response.send({ error: true }); @@ -2323,9 +2347,9 @@ app.post("/exportcharacter", jsonParser, async function (request, response) { try { let json = await charaRead(filename); let stringByteArray = utf8Encode.encode(json).toString(); - let inputWebpPath = `./uploads/${Date.now()}_input.webp`; - let outputWebpPath = `./uploads/${Date.now()}_output.webp`; - let metadataPath = `./uploads/${Date.now()}_metadata.exif`; + let inputWebpPath = path.join(UPLOADS_PATH, `${Date.now()}_input.webp`); + let outputWebpPath = path.join(UPLOADS_PATH, `${Date.now()}_output.webp`); + let metadataPath = path.join(UPLOADS_PATH, `${Date.now()}_metadata.exif`); let metadata = { "Exif": { @@ -2360,7 +2384,10 @@ app.post("/importgroupchat", urlencodedParser, function (request, response) { try { const filedata = request.file; const chatname = humanizedISO8601DateTime(); - fs.copyFileSync(`./uploads/${filedata.filename}`, (`${directories.groupChats}/${chatname}.jsonl`)); + const pathToUpload = path.join(UPLOADS_PATH, filedata.filename); + const pathToNewFile = path.join(directories.groupChats, `${chatname}.jsonl`); + fs.copyFileSync(pathToUpload, pathToNewFile); + fs.unlinkSync(pathToUpload); return response.send({ res: chatname }); } catch (error) { console.error(error); @@ -2379,7 +2406,7 @@ app.post("/importchat", urlencodedParser, function (request, response) { if (filedata) { if (format === 'json') { - fs.readFile(`./uploads/${filedata.filename}`, 'utf8', (err, data) => { + fs.readFile(path.join(UPLOADS_PATH, filedata.filename), 'utf8', (err, data) => { if (err) { console.log(err); @@ -2473,7 +2500,7 @@ app.post("/importchat", urlencodedParser, function (request, response) { } if (format === 'jsonl') { //console.log(humanizedISO8601DateTime()+':imported chat format is JSONL'); - const fileStream = fs.createReadStream('./uploads/' + filedata.filename); + const fileStream = fs.createReadStream(path.join(UPLOADS_PATH, filedata.filename)); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity @@ -2483,7 +2510,7 @@ app.post("/importchat", urlencodedParser, function (request, response) { let jsonData = json5.parse(line); if (jsonData.user_name !== undefined || jsonData.name !== undefined) { - fs.copyFile(`./uploads/${filedata.filename}`, (`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()}.jsonl`), (err) => { + fs.copyFile(path.join(UPLOADS_PATH, filedata.filename), (`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()}.jsonl`), (err) => { if (err) { response.send({ error: true }); return console.log(err); @@ -2512,8 +2539,9 @@ app.post('/importworldinfo', urlencodedParser, (request, response) => { if (request.body.convertedData) { fileContents = request.body.convertedData; } else { - const pathToUpload = path.join('./uploads/', request.file.filename); + const pathToUpload = path.join(UPLOADS_PATH, request.file.filename); fileContents = fs.readFileSync(pathToUpload, 'utf8'); + fs.unlinkSync(pathToUpload); } try { @@ -2565,7 +2593,7 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => { if (!request.file) return response.sendStatus(400); try { - const pathToUpload = path.join('./uploads/' + request.file.filename); + const pathToUpload = path.join(UPLOADS_PATH, request.file.filename); const crop = tryParse(request.query.crop); let rawImg = await jimp.read(pathToUpload); @@ -2585,6 +2613,73 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => { } }); + +/** + * Ensure the directory for the provided file path exists. + * If not, it will recursively create the directory. + * + * @param {string} filePath - The full path of the file for which the directory should be ensured. + */ +function ensureDirectoryExistence(filePath) { + const dirname = path.dirname(filePath); + if (fs.existsSync(dirname)) { + return true; + } + ensureDirectoryExistence(dirname); + fs.mkdirSync(dirname); +} + +/** + * Endpoint to handle image uploads. + * The image should be provided in the request body in base64 format. + * Optionally, a character name can be provided to save the image in a sub-folder. + * + * @route POST /uploadimage + * @param {Object} request.body - The request payload. + * @param {string} request.body.image - The base64 encoded image data. + * @param {string} [request.body.ch_name] - Optional character name to determine the sub-directory. + * @returns {Object} response - The response object containing the path where the image was saved. + */ +app.post('/uploadimage', jsonParser, async (request, response) => { + // Check for image data + if (!request.body || !request.body.image) { + return response.status(400).send({ error: "No image data provided" }); + } + + // Extracting the base64 data and the image format + const match = request.body.image.match(/^data:image\/(png|jpg|webp);base64,(.+)$/); + if (!match) { + return response.status(400).send({ error: "Invalid image format" }); + } + + const [, format, base64Data] = match; + + // Constructing filename and path + let filename = `${Date.now()}.${format}`; + if (request.body.filename) { + filename = `${request.body.filename}.${format}`; + } + + // if character is defined, save to a sub folder for that character + let pathToNewFile = path.join(directories.userImages, filename); + if (request.body.ch_name) { + pathToNewFile = path.join(directories.userImages, request.body.ch_name, filename); + } + + try { + ensureDirectoryExistence(pathToNewFile); + const imageBuffer = Buffer.from(base64Data, 'base64'); + await fs.promises.writeFile(pathToNewFile, imageBuffer); + // send the path to the image, relative to the client folder, which means removing the first folder from the path which is 'public' + pathToNewFile = pathToNewFile.split(path.sep).slice(1).join(path.sep); + response.send({ path: pathToNewFile }); + } catch (error) { + console.log(error); + response.status(500).send({ error: "Failed to save the image" }); + } +}); + + app.post('/getgroups', jsonParser, (_, response) => { const groups = []; @@ -2633,7 +2728,7 @@ app.post('/creategroup', jsonParser, (request, response) => { return response.sendStatus(400); } - const id = Date.now(); + const id = String(Date.now()); const groupMetadata = { id: id, name: request.body.name ?? 'New Group', @@ -2855,6 +2950,26 @@ function invalidateThumbnail(type, file) { } } +function cleanUploads() { + try { + if (fs.existsSync(UPLOADS_PATH)) { + const uploads = fs.readdirSync(UPLOADS_PATH); + + if (!uploads.length) { + return; + } + + console.debug(`Cleaning uploads folder (${uploads.length} files)`); + uploads.forEach(file => { + const pathToFile = path.join(UPLOADS_PATH, file); + fs.unlinkSync(pathToFile); + }); + } + } catch (err) { + console.error(err); + } +} + async function ensureThumbnailCache() { const cacheFiles = fs.readdirSync(directories.thumbnailsBg); @@ -3202,9 +3317,9 @@ async function sendClaudeRequest(request, response) { controller.abort(); }); - let requestPrompt = convertClaudePrompt(request.body.messages, true, true); + let requestPrompt = convertClaudePrompt(request.body.messages, true, !request.body.exclude_assistant); - if (request.body.assistant_prefill) { + if (request.body.assistant_prefill && !request.body.exclude_assistant) { requestPrompt += request.body.assistant_prefill; } @@ -3277,6 +3392,10 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op return sendScaleRequest(request, response_generate_openai); } + if (request.body.use_ai21) { + return sendAI21Request(request, response_generate_openai); + } + let api_url; let api_key_openai; let headers; @@ -3341,7 +3460,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op config.responseType = 'stream'; } - async function makeRequest(config, response_generate_openai, request, retries = 5, timeout = 1000) { + async function makeRequest(config, response_generate_openai, request, retries = 5, timeout = 5000) { try { const response = await axios(config); @@ -3363,7 +3482,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op } } catch (error) { if (error.response && error.response.status === 429 && retries > 0) { - console.log('Out of quota, retrying...'); + console.log(`Out of quota, retrying in ${Math.round(timeout / 1000)}s`); setTimeout(() => { makeRequest(config, response_generate_openai, request, retries - 1); }, timeout); @@ -3466,20 +3585,108 @@ app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_op response_tokenize_openai.send({ "token_count": num_tokens }); }); +async function sendAI21Request(request, response) { + if (!request.body) return response.sendStatus(400); + const controller = new AbortController(); + console.log(request.body.messages) + request.socket.removeAllListeners('close'); + request.socket.on('close', function () { + controller.abort(); + }); + const options = { + method: 'POST', + headers: { + accept: 'application/json', + 'content-type': 'application/json', + Authorization: `Bearer ${readSecret(SECRET_KEYS.AI21)}` + }, + body: JSON.stringify({ + numResults: 1, + maxTokens: request.body.max_tokens, + minTokens: 0, + temperature: request.body.temperature, + topP: request.body.top_p, + stopSequences: request.body.stop_tokens, + topKReturn: request.body.top_k, + frequencyPenalty: { + scale: request.body.frequency_penalty * 100, + applyToWhitespaces: false, + applyToPunctuations: false, + applyToNumbers: false, + applyToStopwords: false, + applyToEmojis: false + }, + presencePenalty: { + scale: request.body.presence_penalty, + applyToWhitespaces: false, + applyToPunctuations: false, + applyToNumbers: false, + applyToStopwords: false, + applyToEmojis: false + }, + countPenalty: { + scale: request.body.count_pen, + applyToWhitespaces: false, + applyToPunctuations: false, + applyToNumbers: false, + applyToStopwords: false, + applyToEmojis: false + }, + prompt: request.body.messages + }), + signal: controller.signal, + }; + + fetch(`https://api.ai21.com/studio/v1/${request.body.model}/complete`, options) + .then(r => r.json()) + .then(r => { + if (r.completions === undefined) { + console.log(r) + } else { + console.log(r.completions[0].data.text) + } + const reply = { choices: [{ "message": { "content": r.completions[0].data.text, } }] }; + return response.send(reply) + }) + .catch(err => { + console.error(err) + return response.send({ error: true }) + }); + +} + +app.post("/tokenize_ai21", jsonParser, function (request, response_tokenize_ai21 = response) { + if (!request.body) return response_tokenize_ai21.sendStatus(400); + const options = { + method: 'POST', + headers: { + accept: 'application/json', + 'content-type': 'application/json', + Authorization: `Bearer ${readSecret(SECRET_KEYS.AI21)}` + }, + body: JSON.stringify({ text: request.body[0].content }) + }; + + fetch('https://api.ai21.com/studio/v1/tokenize', options) + .then(response => response.json()) + .then(response => response_tokenize_ai21.send({ "token_count": response.tokens.length })) + .catch(err => console.error(err)); +}); + app.post("/save_preset", jsonParser, function (request, response) { const name = sanitize(request.body.name); if (!request.body.preset || !name) { return response.sendStatus(400); } - const filename = `${name}.settings`; - const directory = getPresetFolderByApiId(request.body.apiId); + const settings = getPresetSettingsByAPI(request.body.apiId); + const filename = name + settings.extension; - if (!directory) { + if (!settings.folder) { return response.sendStatus(400); } - const fullpath = path.join(directory, filename); + const fullpath = path.join(settings.folder, filename); writeFileAtomicSync(fullpath, JSON.stringify(request.body.preset, null, 4), 'utf-8'); return response.send({ name }); }); @@ -3490,16 +3697,16 @@ app.post("/delete_preset", jsonParser, function (request, response) { return response.sendStatus(400); } - const filename = `${name}.settings`; - const directory = getPresetFolderByApiId(request.body.apiId); + const settings = getPresetSettingsByAPI(request.body.apiId); + const filename = name + settings.extension; - if (!directory) { + if (!settings.folder) { return response.sendStatus(400); } - const fullpath = path.join(directory, filename); + const fullpath = path.join(settings.folder, filename); - if (fs.existsSync) { + if (fs.existsSync(fullpath)) { fs.unlinkSync(fullpath); return response.sendStatus(200); } else { @@ -3519,17 +3726,19 @@ app.post("/savepreset_openai", jsonParser, function (request, response) { return response.send({ name }); }); -function getPresetFolderByApiId(apiId) { +function getPresetSettingsByAPI(apiId) { switch (apiId) { case 'kobold': case 'koboldhorde': - return directories.koboldAI_Settings; + return { folder: directories.koboldAI_Settings, extension: '.settings' }; case 'novel': - return directories.novelAI_Settings; + return { folder: directories.novelAI_Settings, extension: '.settings' }; case 'textgenerationwebui': - return directories.textGen_Settings; + return { folder: directories.textGen_Settings, extension: '.settings' }; + case 'instruct': + return { folder: directories.instruct, extension: '.json' }; default: - return null; + return { folder: null, extension: null }; } } @@ -3623,6 +3832,7 @@ const setupTasks = async function () { ensurePublicDirectoriesExist(); await ensureThumbnailCache(); contentManager.checkForNewContent(); + cleanUploads(); // Colab users could run the embedded tool if (!is_colab) await convertWebp(); @@ -3649,7 +3859,7 @@ const setupTasks = async function () { console.log('Launching...'); if (autorun) open(autorunUrl.toString()); - + console.log('\x1b[32mSillyTavern is listening on: ' + tavernUrl + '\x1b[0m'); if (listen) { @@ -3778,6 +3988,7 @@ const SECRET_KEYS = { DEEPL: 'deepl', OPENROUTER: 'api_key_openrouter', SCALE: 'api_key_scale', + AI21: 'api_key_ai21' } function migrateSecrets() { @@ -4175,7 +4386,7 @@ app.post('/upload_sprite_pack', urlencodedParser, async (request, response) => { return response.sendStatus(404); } - const spritePackPath = path.join("./uploads/", file.filename); + const spritePackPath = path.join(UPLOADS_PATH, file.filename); const sprites = await getImageBuffers(spritePackPath); const files = fs.readdirSync(spritesPath); @@ -4233,7 +4444,7 @@ app.post('/upload_sprite', urlencodedParser, async (request, response) => { } const filename = label + path.parse(file.originalname).ext; - const spritePath = path.join("./uploads/", file.filename); + const spritePath = path.join(UPLOADS_PATH, file.filename); const pathToFile = path.join(spritesPath, filename); // Copy uploaded file to sprites folder fs.cpSync(spritePath, pathToFile);