Merge branch 'staging' of https://github.com/Tony-sama/SillyTavern into staging

This commit is contained in:
Tony Ribeiro 2023-08-20 17:18:27 +02:00
commit a57a3d6188
22 changed files with 2427 additions and 2289 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">
@ -1785,22 +1797,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">
@ -2144,7 +2161,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">
@ -2152,7 +2169,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">
@ -2160,7 +2177,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>
@ -2170,7 +2187,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">
@ -2178,7 +2195,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">
@ -2186,7 +2203,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>
@ -3696,9 +3713,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>
@ -4339,4 +4354,4 @@
</script>
</body>
</html>
</html>

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

@ -2378,10 +2378,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";
@ -2470,6 +2466,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;
@ -5402,9 +5403,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");
@ -5768,7 +5768,6 @@ export async function displayPastChats() {
}
}
}
}
displayChats(''); // Display all by default
@ -7518,7 +7517,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);
});
@ -8002,7 +8001,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();
@ -8010,8 +8011,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;
@ -8022,9 +8024,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

@ -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';
@ -336,25 +337,25 @@ async function getGroups() {
}
export function getGroupBlock(group) {
const template = $("#group_list_template .group_select").clone();
template.data("id", group.id);
template.attr("grid", group.id);
template.find(".ch_name").html(group.name);
template.find('.group_fav_icon').css("display", 'none');
template.addClass(group.fav ? 'is_fav' : '');
template.find(".ch_fav").val(group.fav);
const template = $("#group_list_template .group_select").clone();
template.data("id", group.id);
template.attr("grid", group.id);
template.find(".ch_name").html(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) {
@ -362,17 +363,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>`);
}
@ -1079,8 +1090,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);
@ -1122,9 +1132,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];
@ -1147,16 +1166,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);
@ -1238,6 +1263,11 @@ function updateFavButtonState(state) {
}
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) {
cancelTtsPlay();
@ -1303,7 +1333,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: [],

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

@ -479,9 +479,10 @@ 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 continue nudge
@ -512,7 +513,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');
@ -541,9 +543,8 @@ 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) => {
chatCompletion.insert(newExampleChat, 'dialogueExamples');
dialogue.forEach((prompt, promptIndex) => {
const role = 'system';
const content = prompt.content || '';
@ -556,11 +557,6 @@ function populateDialogueExamples(prompts, chatCompletion) {
}
});
});
chatCompletion.freeBudget(newExampleChat);
const chatExamples = chatCompletion.getMessages().getItemByIdentifier('dialogueExamples').getCollection();
if (chatExamples.length) chatCompletion.insertAtStart(newExampleChat, 'dialogueExamples');
}
}
@ -769,6 +765,12 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world
prompts.set(jbReplacement, prompts.index('jailbreak'));
}
// TODO: Integrate Group nudge into the prompt manager properly
if(selected_group) {
let group_nudge = {"role": "system", "content": `[Write the next reply only as ${name2}]`};
openai_msgs.push(group_nudge);
}
// Allow subscribers to manipulate the prompts object
eventSource.emit(event_types.OAI_BEFORE_CHATCOMPLETION, prompts);
@ -1370,7 +1372,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];
@ -1442,8 +1444,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;
}
@ -1451,6 +1453,7 @@ class Message {
setName(name) {
this.name = name;
this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name });
}
/**

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/',
@ -600,7 +602,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 () {
@ -2612,6 +2614,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 = [];
@ -3392,7 +3461,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);
@ -3414,7 +3483,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);