Merge branch 'staging' into qolfeatures

This commit is contained in:
Cohee 2023-08-20 18:47:43 +03:00
commit a27bef8b12
26 changed files with 2622 additions and 2415 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ public/backgrounds/
public/groups/
public/group chats/
public/worlds/
public/user/
public/css/bg_load.css
public/themes/
public/OpenAI Settings/

View File

@ -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;
}

View File

@ -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%;
}

View File

@ -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;
}
}

227
public/css/rm-groups.css Normal file
View File

@ -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;
}

View File

@ -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;
}

419
public/css/st-tailwind.css Normal file
View File

@ -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;
}

167
public/css/tags.css Normal file
View File

@ -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;
}

View File

@ -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;
}

181
public/css/world-info.css Normal file
View File

@ -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);
} */

View File

@ -48,7 +48,19 @@
<script type="module" src="lib/swiped-events.js"></script>
<script type="module" src="lib/eventemitter.js"></script>
<script type="module" src="scripts/power-user.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="css/st-tailwind.css">
<link rel="stylesheet" type="text/css" href="css/tags.css">
<link rel="stylesheet" type="text/css" href="css/rm-groups.css">
<link rel="stylesheet" type="text/css" href="css/group-avatars.css">
<link rel="stylesheet" type="text/css" href="css/toggle-dependent.css">
<link rel="stylesheet" type="text/css" href="css/world-info.css">
<link rel="stylesheet" type="text/css" href="css/extensions-panel.css">
<link rel="stylesheet" type="text/css" href="css/select2-overrides.css">
<link rel="stylesheet" type="text/css" href="css/mobile-styles.css">
<link rel="stylesheet" href="css/bg_load.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script>
@ -131,7 +143,7 @@
<div class="checked fa-solid fa-lock "></div>
</label>
</div>
<div data-i18n="clickslidertips" class="toggle-description flex-container justifyLeft wide100p editable-slider-notification">
<div data-i18n="clickslidertips" class="toggle-description wide100p editable-slider-notification">
Click slider numbers to input manually.
</div>
<div class="scrollableInner">
@ -151,8 +163,8 @@
</select>
<i data-preset-manager-update="kobold" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
<i data-preset-manager-new="kobold" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
<i data-preset-manager-import="kobold" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
<i data-preset-manager-export="kobold" class="menu_button fa-solid fa-download" title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-import="kobold" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
<i data-preset-manager-export="kobold" class="menu_button fa-solid fa-file-export" title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-delete="kobold" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
</div>
</div>
@ -169,8 +181,8 @@
</select>
<i data-preset-manager-update="novel" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
<i data-preset-manager-new="novel" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
<i data-preset-manager-import="novel" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
<i data-preset-manager-export="novel" class="menu_button fa-solid fa-download" title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-import="novel" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
<i data-preset-manager-export="novel" class="menu_button fa-solid fa-file-export" title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-delete="novel" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
</div>
</div>
@ -183,8 +195,8 @@
</select>
<i id="update_oai_preset" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
<i id="new_oai_preset" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
<i title="Import preset" id="import_oai_preset" class="menu_button fa-solid fa-upload" data-i18n="[title]Import preset"></i>
<i title="Export preset" id="export_oai_preset" class="menu_button fa-solid fa-download" data-i18n="[title]Export preset"></i>
<i title="Import preset" id="import_oai_preset" class="menu_button fa-solid fa-file-import" data-i18n="[title]Import preset"></i>
<i title="Export preset" id="export_oai_preset" class="menu_button fa-solid fa-file-export" data-i18n="[title]Export preset"></i>
<i id="delete_oai_preset" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
<input id="openai_preset_import_file" type="file" accept=".json,.settings" hidden />
</div>
@ -198,8 +210,8 @@
</select>
<i data-preset-manager-update="textgenerationwebui" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
<i data-preset-manager-new="textgenerationwebui" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
<i data-preset-manager-import="textgenerationwebui" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
<i data-preset-manager-export="textgenerationwebui" class="menu_button fa-solid fa-download" title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-import="textgenerationwebui" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
<i data-preset-manager-export="textgenerationwebui" class="menu_button fa-solid fa-file-export" title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-delete="textgenerationwebui" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
</div>
</div>
@ -1609,8 +1621,8 @@
<select id="openai_logit_bias_preset">
</select>
<i title="New preset" id="openai_logit_bias_new_preset" class="menu_button fa-solid fa-plus" data-i18n="[title]New preset"></i>
<i title="Import preset" id="openai_logit_bias_import_preset" class="menu_button fa-solid fa-upload" data-i18n="[title]Import preset"></i>
<i title="Export preset" id="openai_logit_bias_export_preset" class="menu_button fa-solid fa-download" data-i18n="[title]Export preset"></i>
<i title="Import preset" id="openai_logit_bias_import_preset" class="menu_button fa-solid fa-file-import" data-i18n="[title]Import preset"></i>
<i title="Export preset" id="openai_logit_bias_export_preset" class="menu_button fa-solid fa-file-export" data-i18n="[title]Export preset"></i>
<i title="Delete preset" id="openai_logit_bias_delete_preset" class="menu_button fa-solid fa-trash-can" data-i18n="[title]Delete preset"></i>
<input id="openai_logit_bias_import_file" type="file" accept=".json" hidden />
</div>
@ -1793,22 +1805,27 @@
<input id="use-mancer-api-checkbox" type="checkbox" />
</label>
</div>
<div id="mancer-api-ui" style="display:none;">
<h4 data-i18n="Mancer API key">Mancer API key</h4>
<div class="flex-container">
<input id="api_key_mancer" name="api_key_mancer" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_mancer">
</div>
</div>
</div>
<div>
<div class="flex-container flexFlowColumn">
<div id="mancer_api_subpanel" class="flex-container flexFlowColumn" style="display:none;">
<h4 data-i18n="Mancer API key">Mancer API key</h4>
<div class="flex-container">
<input id="api_key_mancer" name="api_key_mancer" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_mancer">
</div>
</div>
<div data-for="api_key_mancer" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<div class="flex1">
<h4 data-i18n="Mancer API url">Mancer API url</h4>
<small>Example: https://neuro.mancer.tech/webui/MODEL/api</small>
<input id="mancer_api_url_text" name="mancer_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
</div>
</div>
<div id="tgwebui_api_subpanel" class="flex-container flexFlowColumn">
<div class="flex1">
<h4 data-i18n="Blocking API url">Blocking API url</h4>
<small>Example: http://127.0.0.1:5000/</small>
<small>Example: http://127.0.0.1:5000/api</small>
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
</div>
<div class="flex1">
@ -2152,7 +2169,7 @@
<small data-i18n="Input Sequence">Input Sequence</small>
</label>
<div>
<textarea id="instruct_input_sequence" class="text_pole textarea_compact" maxlength="500" rows="1"></textarea>
<textarea id="instruct_input_sequence" class="text_pole textarea_compact" maxlength="500" rows="2"></textarea>
</div>
</div>
<div class="flex1">
@ -2160,7 +2177,7 @@
<small data-i18n="Output Sequence">Output Sequence</small>
</label>
<div>
<textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="1"></textarea>
<textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
</div>
</div>
<div class="flex1">
@ -2168,7 +2185,7 @@
<small data-i18n="Last Sequence">Last Sequence</small>
</label>
<div>
<textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="1"></textarea>
<textarea id="instruct_last_output_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
</div>
</div>
</div>
@ -2178,7 +2195,7 @@
<small data-i18n="System Sequence">System Sequence</small>
</label>
<div>
<textarea id="instruct_system_sequence" class="text_pole textarea_compact" maxlength="500" rows="1"></textarea>
<textarea id="instruct_system_sequence" class="text_pole textarea_compact" maxlength="500" rows="2"></textarea>
</div>
</div>
<div class="flex1">
@ -2186,7 +2203,7 @@
<small data-i18n="Stop Sequence">Stop Sequence</small>
</label>
<div>
<textarea id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="1"></textarea>
<textarea id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
</div>
</div>
<div class="flex1">
@ -2194,7 +2211,7 @@
<small data-i18n="Separator">Separator</small>
</label>
<div>
<textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="1"></textarea>
<textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" maxlength="500" rows="2"></textarea>
</div>
</div>
</div>
@ -3286,7 +3303,7 @@
<div class="inline-drawer-content">
<div name="Unadded Char List" class="flex-container flexFlowColumn overflowYAuto flex1">
<div id="rm_group_add_members_header">
<input id="rm_group_filter" class="text_pole margin0" type="search" data-i18n="[placeholder]Filter..." placeholder="Filter..." maxlength="100" />
<input id="rm_group_filter" class="text_pole margin0" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." maxlength="100" />
</div>
<div class="rm_tag_controls">
<div class="tags rm_tag_filter"></div>
@ -3310,7 +3327,7 @@
<div id="charListFixedTop">
<form id="form_character_search_form" action="javascript:void(null);">
<div id="rm_button_create" title="Create New Character" data-i18n="[title]Create New Character" class="menu_button fa-solid fa-user-plus "></div>
<div id="character_import_button" title="Import Character from File" data-i18n="[title]Import Character from File" class="menu_button fa-solid fa-file-arrow-up faSmallFontSquareFix"></div>
<div id="character_import_button" title="Import Character from File" data-i18n="[title]Import Character from File" class="menu_button fa-solid fa-file-import faSmallFontSquareFix"></div>
<div id="external_import_button" title="Import content from external URL" data-i18n="[title]Import content from external URL" class="menu_button fa-solid fa-cloud-arrow-down faSmallFontSquareFix"></div>
<div id="rm_button_group_chats" title="Create New Chat Group" data-i18n="[title]Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div>
<input id="character_search_bar" class="text_pole width100p" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." maxlength="50" />
@ -3467,7 +3484,7 @@
<div id="select_chat_popup">
<input type="text" id="select_chat_search" placeholder="Search..." autocomplete="off">
<div id="select_chat_import"> <!-- import chat popup header -->
<div id="chat_import_button" class="fa-solid fa-file-arrow-up menu_button"></div>
<div id="chat_import_button" class="fa-solid fa-file-import menu_button"></div>
<div id="selectChatPopupHeaderText" class="TxtLrgBoldCenter">
<span id="ChatHistoryCharName"></span>
<br>
@ -3708,9 +3725,7 @@
<small>
<span data-i18n="Content">
Content
<span>(Tokens:&nbsp;
<span class="world_entry_form_token_counter">0</span>
)
<span>(Tokens:&nbsp; <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)
</span>
</span>
</small>

View File

@ -1,13 +1,13 @@
{
"input_sequence": "### Instruction:",
"last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
"input_sequence": "\n### Instruction:",
"last_output_sequence": "\n### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
"macro": true,
"name": "Roleplay",
"names": true,
"output_sequence": "### Response:",
"output_sequence": "\n### 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.",
"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",
"system_sequence": "",
"wrap": true
}

View File

@ -752,7 +752,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 = "";
@ -1779,7 +1779,6 @@ function scrollChatToBottom() {
function substituteParams(content, _name1, _name2, _original, _group) {
_name1 = _name1 ?? name1;
_name2 = _name2 ?? name2;
_original = _original || '';
_group = _group ?? name2;
if (!content) {
@ -2136,6 +2135,8 @@ function baseChatReplace(value, name1, name2) {
if (power_user.collapse_newlines) {
value = collapseNewlines(value);
}
value = value.replace(/\r/g, '');
}
return value;
}
@ -2387,10 +2388,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";
@ -2479,6 +2476,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;
@ -2538,8 +2540,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
let charPersonality = baseChatReplace(characters[this_chid].personality.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) : '';
// Parse example messages
if (!mesExamples.startsWith('<START>')) {
@ -2995,8 +2997,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 };
@ -5413,9 +5416,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");
@ -5779,7 +5781,6 @@ export async function displayPastChats() {
}
}
}
}
displayChats(''); // Display all by default
@ -7529,7 +7530,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);
});
@ -8013,7 +8014,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();
@ -8021,8 +8024,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.<br/>WebUI URLs should end with <tt>/api</tt><br/>Enable 'Relaxed API URLs' to allow other paths.", 'text');
return;
@ -8033,9 +8037,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();

View File

@ -1,7 +1,9 @@
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 { TokenHandler } from "./openai.js";
import { power_user } from "./power-user.js";
import { debounce, waitUntilCondition } from "./utils.js";
function debouncePromise(func, delay) {
@ -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');
}
}
@ -250,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);
@ -271,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
@ -382,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());
}
@ -390,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)) {
@ -427,7 +431,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
let promptOrder = [];
if ('global' === this.configuration.promptOrder.strategy) {
promptOrder = this.getPromptOrderForCharacter({id: this.configuration.promptOrder.dummyId});
promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId });
} else if ('character' === this.configuration.promptOrder.strategy) {
promptOrder = [];
} else {
@ -490,10 +494,10 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
};
reader.readAsText(file);
});
});
fileOpener.click();
});
fileOpener.click();
});
}
// Restore default state of a characters prompt order
@ -556,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();
@ -590,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(() => {
@ -604,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
@ -652,7 +661,7 @@ PromptManagerModule.prototype.updatePrompts = function (prompts) {
})
}
PromptManagerModule.prototype.getTokenHandler = function() {
PromptManagerModule.prototype.getTokenHandler = function () {
return this.tokenHandler;
}
@ -666,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 });
}
/**
@ -713,7 +722,7 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () {
this.serviceSettings.prompt_order = this.serviceSettings.prompt_order ?? [];
if ('global' === this.configuration.promptOrder.strategy) {
const dummyCharacter = {id: this.configuration.promptOrder.dummyId};
const dummyCharacter = { id: this.configuration.promptOrder.dummyId };
const promptOrder = this.getPromptOrderForCharacter(dummyCharacter);
if (0 === promptOrder.length) this.addPromptOrderForCharacter(dummyCharacter, promptManagerDefaultPromptOrder);
@ -729,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);
}
}
}
@ -745,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 => {
@ -815,10 +824,10 @@ PromptManagerModule.prototype.handleCharacterDeleted = function (event) {
*/
PromptManagerModule.prototype.handleCharacterSelected = function (event) {
if ('global' === this.configuration.promptOrder.strategy) {
this.activeCharacter = {id: this.configuration.promptOrder.dummyId};
} else if ('character' === 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};
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.
@ -836,11 +845,11 @@ PromptManagerModule.prototype.handleCharacterSelected = function (event) {
*/
PromptManagerModule.prototype.handleCharacterUpdated = function (event) {
if ('global' === this.configuration.promptOrder.strategy) {
this.activeCharacter = {id: this.configuration.promptOrder.dummyId};
this.activeCharacter = { id: this.configuration.promptOrder.dummyId };
} else if ('character' === this.configuration.promptOrder.strategy) {
this.activeCharacter = {id: event.detail.id, ...event.detail.character};
this.activeCharacter = { id: event.detail.id, ...event.detail.character };
} else {
throw new Error ('Prompt order strategy not supported.')
throw new Error('Prompt order strategy not supported.')
}
}
@ -851,15 +860,15 @@ PromptManagerModule.prototype.handleCharacterUpdated = function (event) {
*/
PromptManagerModule.prototype.handleGroupSelected = function (event) {
if ('global' === this.configuration.promptOrder.strategy) {
this.activeCharacter = {id: this.configuration.promptOrder.dummyId};
this.activeCharacter = { id: this.configuration.promptOrder.dummyId };
} else if ('character' === this.configuration.promptOrder.strategy) {
const characterDummy = {id: event.detail.id, group: event.detail.group};
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)
} else {
throw new Error ('Prompt order strategy not supported.')
throw new Error('Prompt order strategy not supported.')
}
}
@ -868,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('.')));
}
@ -982,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 = `<div class="range-block m-t-1">
@ -1006,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;
}
@ -1018,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);
}
@ -1111,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');
@ -1150,7 +1159,7 @@ 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);
@ -1163,7 +1172,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 => {
@ -1182,7 +1191,7 @@ 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;
@ -1246,8 +1255,8 @@ PromptManagerModule.prototype.renderPromptManager = function () {
</select>
<a class="menu_button fa-chain fa-solid" title="Insert prompt" data-i18n="Insert"></a>
<a class="caution menu_button fa-x fa-solid" title="Delete prompt" data-i18n="Delete"></a>
<a class="menu_button fa-file-arrow-down fa-solid" id="prompt-manager-export" title="Export this prompt list" data-i18n="Export"></a>
<a class="menu_button fa-file-arrow-up fa-solid" id="prompt-manager-import" title="Import a prompt list" data-i18n="Import"></a>
<a class="menu_button fa-file-import fa-solid" id="prompt-manager-import" title="Import a prompt list" data-i18n="Import"></a>
<a class="menu_button fa-file-export fa-solid" id="prompt-manager-export" title="Export this prompt list" data-i18n="Export"></a>
<a class="menu_button fa-undo fa-solid" id="prompt-manager-reset-character" title="Reset current character" data-i18n="Reset current character"></a>
<a class="menu_button fa-plus-square fa-solid" title="New prompt" data-i18n="New"></a>
</div>
@ -1270,9 +1279,9 @@ PromptManagerModule.prototype.renderPromptManager = function () {
<a class="export-promptmanager-prompts-full list-group-item" data-i18n="Export all">Export all</a>
<span class="tooltip fa-solid fa-info-circle" title="Export all your prompts to a file"></span>
</div>
${ 'global' === this.configuration.promptOrder.strategy
? ''
: `<div class="row">
${'global' === this.configuration.promptOrder.strategy
? ''
: `<div class="row">
<a class="export-promptmanager-prompts-character list-group-item" data-i18n="Export for character">Export
for character</a>
<span class="tooltip fa-solid fa-info-circle"
@ -1287,7 +1296,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 = () => {
@ -1306,14 +1315,46 @@ PromptManagerModule.prototype.renderPromptManager = function () {
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
*/
@ -1323,7 +1364,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
const promptManagerList = this.listElement;
promptManagerList.innerHTML = '';
const {prefix} = this.configuration;
const { prefix } = this.configuration;
let listItemHtml = `
<li class="${prefix}prompt_manager_list_head">
@ -1350,7 +1391,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;
@ -1399,7 +1440,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
${prompt.marker ? '<span class="fa-solid fa-thumb-tack" title="Marker"></span>' : ''}
${!prompt.marker && prompt.system_prompt ? '<span class="fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''}
${!prompt.marker && !prompt.system_prompt ? '<span class="fa-solid fa-user" title="User Prompt"></span>' : ''}
${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${prompt.name}</a>` : prompt.name }
${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${prompt.name}</a>` : prompt.name}
</span>
<span>
<span class="prompt_manager_prompt_controls">
@ -1449,7 +1490,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;
@ -1502,7 +1543,7 @@ PromptManagerModule.prototype.import = function (importData) {
let promptOrder = [];
if ('global' === this.configuration.promptOrder.strategy) {
const promptOrder = this.getPromptOrderForCharacter({id: this.configuration.promptOrder.dummyId});
const promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId });
Object.assign(promptOrder, importData.data.prompt_order);
this.log(`Prompt order import succeeded`);
} else if ('character' === this.configuration.promptOrder.strategy) {
@ -1526,7 +1567,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;
@ -1549,7 +1590,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());
@ -1571,9 +1612,9 @@ PromptManagerModule.prototype.makeDraggable = function () {
$(`#${this.configuration.prefix}prompt_manager_list`).sortable({
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));
@ -1583,7 +1624,8 @@ PromptManagerModule.prototype.makeDraggable = function () {
this.log(`Prompt order updated for ${this.activeCharacter.name}.`);
this.saveServiceSettings();
}});
}
});
};
/**
@ -1594,7 +1636,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');
}
@ -1604,7 +1646,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');
}

View File

@ -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");
@ -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');
@ -933,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();
});

View File

@ -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);

View File

@ -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;
}

View File

@ -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<void>} - 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<void>} - 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");

View File

@ -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,
@ -309,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 = [];
}
@ -334,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).css('color', group.fav ? 'gold' : 'white');
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) {
@ -360,17 +364,27 @@ 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) {
console.trace(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 $(`<div class="avatar"><img src="${default_avatar}"></div>`);
}
if (isDataURL(group.avatar_url)) {
// if isDataURL or if it's a valid local file url
if (isValidImageUrl(group.avatar_url)) {
return $(`<div class="avatar"><img src="${group.avatar_url}"></div>`);
}
@ -1090,8 +1104,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);
@ -1133,9 +1146,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<void>} - A promise that resolves when the processing and upload is complete.
*/
async function uploadGroupAvatar(event) {
const file = event.target.files[0];
@ -1158,16 +1180,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);
@ -1248,8 +1276,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) {
@ -1290,16 +1321,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() {
@ -1324,7 +1347,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: [],
@ -1570,7 +1593,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);

View File

@ -578,7 +578,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
@ -586,7 +586,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) {

View File

@ -19,7 +19,6 @@ import {
system_message_types,
replaceBiasMarkup,
is_send_press,
saveSettings,
Generate,
main_api,
eventSource,
@ -45,6 +44,7 @@ import {
} from "./secrets.js";
import {
deepClone,
delay,
download,
getFileText, getSortableDelay,
@ -390,7 +390,7 @@ function setupChatCompletionPromptManager(openAiSettings) {
promptManager.tokenHandler = tokenHandler;
promptManager.init(configuration, openAiSettings);
promptManager.render();
promptManager.render(false);
return promptManager;
}
@ -480,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 = new Message.fromPrompt(prompts.get('groupNudge'));
chatCompletion.reserveBudget(groupNudgeMessage);
}
// Reserve budget for continue nudge
let continueMessage = null;
if (type === 'continue' && cyclePrompt) {
@ -513,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');
@ -525,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);
@ -542,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 || '';
@ -554,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');
}
}
@ -700,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 = [
@ -716,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
@ -753,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;
@ -763,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;
@ -807,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];
@ -820,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 });
@ -1371,7 +1390,7 @@ 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];
@ -1443,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;
}
@ -1452,6 +1471,7 @@ class Message {
setName(name) {
this.name = name;
this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name });
}
/**
@ -1663,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.
*
@ -2349,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');
}

View File

@ -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) {

View File

@ -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<string>} - 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();

View File

@ -464,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;
@ -472,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);

File diff suppressed because it is too large Load Diff

View File

@ -297,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/',
@ -599,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 () {
@ -2611,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 = [];
@ -2659,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',
@ -3391,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);
@ -3413,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);