mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into staging
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,12 +6,14 @@ public/backgrounds/
|
||||
public/groups/
|
||||
public/group chats/
|
||||
public/worlds/
|
||||
public/user/
|
||||
public/css/bg_load.css
|
||||
public/themes/
|
||||
public/OpenAI Settings/
|
||||
public/KoboldAI Settings/
|
||||
public/NovelAI Settings/
|
||||
public/TextGen Settings/
|
||||
public/instruct/
|
||||
public/scripts/extensions/third-party/
|
||||
public/stats.json
|
||||
/uploads/
|
||||
|
@@ -133,7 +133,7 @@
|
||||
"output_sequence": "### Response:",
|
||||
"preset": "Alpaca",
|
||||
"separator_sequence": "",
|
||||
"macro": false
|
||||
"macro": true
|
||||
},
|
||||
"personas": {},
|
||||
"default_persona": null,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Default",
|
||||
"story_string": "{{#if description}}{{description}}{{/if}}\n{{#if personality}}{{personality}}{{/if}}\n{{#if scenario}}Scenario: {{scenario}}{{/if}}",
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "***",
|
||||
"example_separator": "***"
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Pygmalion",
|
||||
"story_string": "{{#if description}}{{{char}}}'s Persona: {{description}}{{/if}}\n{{#if personality}}Personality: {{personality}}{{/if}}\n{{#if scenario}}Scenario: {{scenario}}{{/if}}",
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if description}}{{{char}}}'s Persona: {{description}}\n{{/if}}{{#if personality}}Personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "<START>",
|
||||
"example_separator": "<START>"
|
||||
}
|
||||
|
6
public/context/Roleplay.json
Normal file
6
public/context/Roleplay.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Roleplay",
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}### Input:\n{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "### New Roleplay:",
|
||||
"example_separator": "### New Roleplay:"
|
||||
}
|
6
public/context/simple-proxy-for-tavern.json
Normal file
6
public/context/simple-proxy-for-tavern.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "simple-proxy-for-tavern",
|
||||
"story_string": "## {{char}}\n- You're \"{{char}}\" in this never-ending roleplay with \"{{user}}\".\n### Input:\n{{#if system}}{{system}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}### Response:\n(OOC) Understood. I will take this info into account for the roleplay. (end OOC)",
|
||||
"chat_start": "### New Roleplay:",
|
||||
"example_separator": "### New Roleplay:"
|
||||
}
|
96
public/css/extensions-panel.css
Normal file
96
public/css/extensions-panel.css
Normal 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;
|
||||
}
|
91
public/css/group-avatars.css
Normal file
91
public/css/group-avatars.css
Normal 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%;
|
||||
}
|
419
public/css/mobile-styles.css
Normal file
419
public/css/mobile-styles.css
Normal 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
227
public/css/rm-groups.css
Normal 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;
|
||||
}
|
134
public/css/select2-overrides.css
Normal file
134
public/css/select2-overrides.css
Normal 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
419
public/css/st-tailwind.css
Normal 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
167
public/css/tags.css
Normal 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;
|
||||
}
|
367
public/css/toggle-dependent.css
Normal file
367
public/css/toggle-dependent.css
Normal 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
181
public/css/world-info.css
Normal 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);
|
||||
} */
|
570
public/i18n.json
570
public/i18n.json
@@ -2,7 +2,8 @@
|
||||
"lang": [
|
||||
"zh-cn",
|
||||
"ja-jp",
|
||||
"ko-kr"
|
||||
"ko-kr",
|
||||
"ru-ru"
|
||||
],
|
||||
"zh-cn": {
|
||||
"clickslidertips": "点击滑块右侧数字可手动输入",
|
||||
@@ -362,8 +363,8 @@
|
||||
"Not Connected": "未连接",
|
||||
"Persona Management": "用户角色设置",
|
||||
"Persona Description": "用户角色描述",
|
||||
"Before Character Card": "角色卡之前",
|
||||
"After Character Card": "角色卡之后",
|
||||
"In Story String / Chat Completion: Before Character Card": "在故事串中 / Chat Completion: 角色卡之前",
|
||||
"In Story String / Chat Completion: After Character Card": "在故事串中 / Chat Completion: 角色卡之后",
|
||||
"Top of Author's Note": "作者注释之前",
|
||||
"Bottom of Author's Note": "作者注释之后",
|
||||
"How do I use this?": "用户角色设置说明",
|
||||
@@ -914,8 +915,8 @@
|
||||
"Not Connected": "NEEDS TRANSLATION",
|
||||
"Persona Management": "NEEDS TRANSLATION",
|
||||
"Persona Description": "NEEDS TRANSLATION",
|
||||
"Before Character Card": "NEEDS TRANSLATION",
|
||||
"After Character Card": "NEEDS TRANSLATION",
|
||||
"In Story String / Chat Completion: Before Character Card": "NEEDS TRANSLATION",
|
||||
"In Story String / Chat Completion: After Character Card": "NEEDS TRANSLATION",
|
||||
"Top of Author's Note": "NEEDS TRANSLATION",
|
||||
"Bottom of Author's Note": "NEEDS TRANSLATION",
|
||||
"How do I use this?": "NEEDS TRANSLATION",
|
||||
@@ -1471,8 +1472,8 @@
|
||||
"Not Connected": "접속되지 않음",
|
||||
"Persona Management": "주인공 관리",
|
||||
"Persona Description": "주인공 묘사",
|
||||
"Before Character Card": "캐릭터 카드 앞에",
|
||||
"After Character Card": "캐릭터 카드 다음에",
|
||||
"In Story String / Chat Completion: Before Character Card": "스토리 문자열에서 / 문장완성: 캐릭터 카드 앞에",
|
||||
"In Story String / Chat Completion: After Character Card": "스토리 문자열에서 / 문장완성: 캐릭터 카드 다음에",
|
||||
"Top of Author's Note": "글쓴이 쪽지 위에",
|
||||
"Bottom of Author's Note": "글쓴이 쪽지 밑에",
|
||||
"How do I use this?": "이건 어떻게 써먹나요?",
|
||||
@@ -1671,5 +1672,560 @@
|
||||
"Select this as default persona for the new chats.": "새로 열리는 채팅에서 기본 주인공으로 설정",
|
||||
"Change persona image": "주인공 아바타 바꾸기",
|
||||
"Delete persona": "주인공 삭제하기"
|
||||
},
|
||||
"ru-ru": {
|
||||
"clickslidertips": "Можно установить вручную, использовав цифру рядом с ползунком",
|
||||
"kobldpresets": "Предустановки Kobold",
|
||||
"guikoboldaisettings": "Интерфейс KoboldAI",
|
||||
"novelaipreserts": "Предустановки NovelAI",
|
||||
"default": "По умолчанию",
|
||||
"openaipresets": "Предустановки OpenAI",
|
||||
"text gen webio(ooba) presets": "Предустановки WebUI(ooba)",
|
||||
"response legth(tokens)": "Длина ответа (в токенах)",
|
||||
"select": "Выбрать",
|
||||
"context size(tokens)": "Размер контекста (в токенах)",
|
||||
"unlocked": "Неограниченный",
|
||||
"only select modls support context sizes greater than 2048 tokens. proceed only is you know you're doing": "Только отдельные модели поддерживают контекст, превышающий 2048 токенов. Используйте только если понимаете, что делаете.",
|
||||
"rep.pen": "Rep. Pen.",
|
||||
"rep.pen range": "Диапазон Rep. Pen.",
|
||||
"temperature": "Температура",
|
||||
"Encoder Rep. Pen.": "Расшифровщик Rep. Pen.",
|
||||
"No Repeat Ngram Size": "No Repeat Ngram Size",
|
||||
"Min Length": "Минимальная длина",
|
||||
"OpenAI Reverse Proxy": "Прокси с OpenAI",
|
||||
"Alternative server URL (leave empty to use the default value).": "Альтернативный URL сервера (оставьте пустым для стандартного значения)",
|
||||
"Remove your real OAI API Key from the API panel BEFORE typing anything into this box": "Удалите свой личный OAI API Key из панели API прежде, чем вносить сюда ЧТО УГОДНО",
|
||||
"We cannot provide support for problems encountered while using an unofficial OpenAI proxy": "Мы не сможем предоставить помощь с проблемами, с которыми вы столкнетесь при использовании неофициальных прокси для OpenAI",
|
||||
"Legacy Streaming Processing": "Старый способ потокового вывода текста",
|
||||
"Enable this if the streaming doesn't work with your proxy": "Включите это, если потоковый вывод текста не работает с вашим прокси",
|
||||
"Context Size (tokens)": "Размер контекста (в токенах)",
|
||||
"Max Response Length (tokens)": "Максимальная длина ответа (в токенах)",
|
||||
"Temperature": "Temperature",
|
||||
"Frequency Penalty": "Frequency Penalty",
|
||||
"Presence Penalty": "Presence Penalty",
|
||||
"Top-p": "Top-p",
|
||||
"Display bot response text chunks as they are generated": "Отображать ответ ИИ по мере генерации текста",
|
||||
"Top A": "Top-a",
|
||||
"Typical Sampling": "Typical Sampling",
|
||||
"Tail Free Sampling": "Tail Free Sampling",
|
||||
"Rep. Pen. Slope": "Rep. Pen. Slope",
|
||||
"Single-line mode": "Режим одной строки",
|
||||
"Top K": "Top-k",
|
||||
"Top P": "Top-p",
|
||||
"Do Sample": "Do Sample",
|
||||
"Add BOS Token": "Добавить BOS-токен",
|
||||
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative.": "Добавлять BOS-токен в начале инструкции. Выключение этого может сделать ответы более креативными. ",
|
||||
"Ban EOS Token": "Заблокировать EOS-токен",
|
||||
"Ban the eos_token. This forces the model to never end the generation prematurely": "Блокировка EOS-токена вынудит модель никогда не завершать генерацию преждевременно",
|
||||
"Skip Special Tokens": "Пропускать специальные токены",
|
||||
"Beam search": "Поиск Beam",
|
||||
"Number of Beams": "Количество Beam",
|
||||
"Length Penalty": "Length Penalty",
|
||||
"Early Stopping": "Преждевременная остановка",
|
||||
"Contrastive search": "Contrastive search",
|
||||
"Penalty Alpha": "Penalty Alpha",
|
||||
"Seed": "Зерно",
|
||||
"Inserts jailbreak as a last system message.": "Вставлять JailBreak последним системным сообщением.",
|
||||
"This tells the AI to ignore its usual content restrictions.": "Сообщает AI о необходимости игнорировать стандартные ограничения контента.",
|
||||
"NSFW Encouraged": "Поощрять NSFW",
|
||||
"Tell the AI that NSFW is allowed.": "Сообщает AI, что ему позволено генерировать NSFW.",
|
||||
"NSFW Prioritized": "Предпочитать NSFW",
|
||||
"NSFW prompt text goes first in the prompt to emphasize its effect.": "Отправлять NSFW-инструкцию в начале для усиления его эффекта",
|
||||
"Streaming": "Потоковый вывод текста",
|
||||
"Display the response bit by bit as it is generated.": "Отображать ответ по кускам в процессе генерации.",
|
||||
"When this is off, responses will be displayed all at once when they are complete.": "Если данная функция отключена, ответ будет отображен полностью после генерации.",
|
||||
"Enhance Definitions": "Улучшенная узнаваемость",
|
||||
"Use OAI knowledge base to enhance definitions for public figures and known fictional characters": "Позволяет использовать базу знаний, улучшающую узнаваемость ИИ публичных лиц и вымышленных персонажей",
|
||||
"Wrap in Quotes": "Заключать в кавычки",
|
||||
"Wrap entire user message in quotes before sending.": "Заключить всё сообщение пользователя в кавычки перед отправкой.",
|
||||
"Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.",
|
||||
"Main prompt": "Основная инструкция",
|
||||
"The main prompt used to set the model behavior": "Основная инструкция, используемая для установки поведения модели",
|
||||
"NSFW prompt": "NSFW-инструкция",
|
||||
"Prompt that is used when the NSFW toggle is on": "Инструкция, отправляемая ИИ при включенном поощрении NSFW.",
|
||||
"Jailbreak prompt": "Инструкция для JailBreak",
|
||||
"Prompt that is used when the Jailbreak toggle is on": "Инструкция, отправляемая ИИ при включенном JailBreak.",
|
||||
"Impersonation prompt": "Инструкция для перевоплощения",
|
||||
"Prompt that is used for Impersonation function": "Инструкция, отправляемая ИИ для генерации действий за пользователя",
|
||||
"Logit Bias": "Logit Bias",
|
||||
"Helps to ban or reenforce the usage of certain words": "Позволяет запретить или поощрять использование определенных слов",
|
||||
"View / Edit bias preset": "Посмотреть/Настроить предустановку для bias",
|
||||
"Add bias entry": "Добавить инструкцию в Bias",
|
||||
"Jailbreak activation message": "Сообщение об активации JailBreak",
|
||||
"Message to send when auto-jailbreak is on.": "Сообщение, отправляемое когда автоматический JailBreak включен.",
|
||||
"Jailbreak confirmation reply": "Подтверждение JailBreak",
|
||||
"Bot must send this back to confirm jailbreak": "Это сообщение будет отправлено ИИ при успешном включении JailBreak.",
|
||||
"Character Note": "Заметки о персонаже",
|
||||
"Influences bot behavior in its responses": "Влияет на поведение ИИ и его ответы.",
|
||||
"API": "API",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Use Horde": "Использовать Horde",
|
||||
"API url": "API URL",
|
||||
"Register a Horde account for faster queue times": "Заведите учетную запись Horde для ускорения генерации",
|
||||
"Learn how to contribute your idle GPU cycles to the Hord": "Узнайте подробнее о том, как использовать время простоя GPU для Hord",
|
||||
"Adjust context size to worker capabilities": "Уточнить размер контекста для возможностей рабочих",
|
||||
"Adjust response length to worker capabilities": "Уточнить длинну ответа для возможностей рабочий",
|
||||
"API key": "API-ключ",
|
||||
"Register": "Регист",
|
||||
"For privacy reasons": "В целях конфиденциальности API-ключ будет скрыт после перезагрузки страницы",
|
||||
"Model": "Модель",
|
||||
"Hold Control / Command key to select multiple models.": "Удерживайте Control / Command для выбора нескольких моделей.",
|
||||
"Horde models not loaded": "Модели Horde не загружены",
|
||||
"Not connected": "Нет подключения",
|
||||
"Novel API key": "API-ключ для NovelAI",
|
||||
"Follow": "Следуйте",
|
||||
"these directions": "данным инструкциям",
|
||||
"to get your NovelAI API key.": "чтобы получить свой API-ключ от NovelAI",
|
||||
"Enter it in the box below": "Введите это в окошко ниже",
|
||||
"Novel AI Model": "Модель NovelAI",
|
||||
"Euterpe": "Euterpe",
|
||||
"Krake": "Krake",
|
||||
"No connection": "Нет подключения",
|
||||
"oobabooga/text-generation-webui": "",
|
||||
"Make sure you run it with": "Убедитесь, что при запуске указали аргумент --api",
|
||||
"Blocking API url": "Блокирующий API URL",
|
||||
"Streaming API url": "Потоковый API URL",
|
||||
"to get your OpenAI API key.": "для получения API-ключа OpenAI",
|
||||
"OpenAI Model": "Модель OpenAI",
|
||||
"View API Usage Metrics": "Посмотреть статистику использования API",
|
||||
"Bot": "Бот:",
|
||||
"Connect to the API": "Соединение с API",
|
||||
"Auto-connect to Last Server": "Автоматическое подключение к последнему серверу",
|
||||
"View hidden API keys": "Посмотреть скрытые API-ключи",
|
||||
"Advanced Formatting": "Расширенное форматирование",
|
||||
"AutoFormat Overrides": "Замена АвтоФормата",
|
||||
"Disable description formatting": "Отключить форматирование описания",
|
||||
"Disable personality formatting": "Отключить форматирование личности",
|
||||
"Disable scenario formatting": "Отключить форматирование сценария",
|
||||
"Disable example chats formatting": "Отключить форматирование примеров чата",
|
||||
"Disable chat start formatting": "Отключить форматирование начала чата",
|
||||
"Custom Chat Separator": "Пользовательское разделение чата",
|
||||
"Instruct mode": "Режим Instruct",
|
||||
"Enabled": "Включен",
|
||||
"Wrap Sequences with Newline": "Отделять последовательности красной строкой",
|
||||
"Include Names": "Показывать имена",
|
||||
"System Prompt": "Системная инструкция",
|
||||
"Input Sequence": "Input Sequence",
|
||||
"Output Sequence": "Output Sequence",
|
||||
"System Sequence": "System Sequence",
|
||||
"Stop Sequence": "Stop Sequence",
|
||||
"Context Formatting": "Форматирование контекста",
|
||||
"Tokenizer": "Токенайзер",
|
||||
"None / Estimated": "Отсутствует/Приблизительно",
|
||||
"Sentencepiece (LLaMA)": "Sentencepiece(LLaMA)",
|
||||
"Token Padding": "Заполнение токенов",
|
||||
"Always add character's name to prompt": "Всегда добавлять имя персонажа в инструкции",
|
||||
"Keep Example Messages in Prompt": "Сохранять примеры сообщений в инструкции",
|
||||
"Remove Empty New Lines from Output": "Удалять пустые строчки из вывода",
|
||||
"Pygmalion Formatting": "Форматирование Pygmalion",
|
||||
"Disabled for all models": "Выключено для всех моделей",
|
||||
"Automatic (based on model name)": "Автоматически (выбор по названию модели)",
|
||||
"Enabled for all models": "Включить для всех моделей",
|
||||
"Multigen": "Мултиген",
|
||||
"First chunk (tokens)": "Первый отрезок (в токенах)",
|
||||
"Next chunks (tokens)": "Следующий отрезок (в токенах)",
|
||||
"Anchors Order": "Порядок Anchors",
|
||||
"Character then Style": "Персонаж после Стиля",
|
||||
"Style then Character": "Стиль после Персонажа",
|
||||
"Character Anchor": "Anchors Персонажа",
|
||||
"Style Anchor": "Стиль Anchors",
|
||||
"World Info": "Информация о мире",
|
||||
"Scan Depth": "Глубина сканирования",
|
||||
"depth": "глубина",
|
||||
"Token Budget": "Объем токенов",
|
||||
"budget": "объем",
|
||||
"Recursive scanning": "Рекурсивное сканирование",
|
||||
"Soft Prompt": "Мягкая инструкция",
|
||||
"About soft prompts": "О мягких инструкциях",
|
||||
"None": "Отсутствует",
|
||||
"User Settings": "Настройки пользователя",
|
||||
"UI Customization": "Настройки UI",
|
||||
"Avatar Style": "Стиль аватаров",
|
||||
"Circle": "Круглые",
|
||||
"Rectangle": "Прямоугольные",
|
||||
"Chat Style": "Стиль чата",
|
||||
"Default": "По умолчанию",
|
||||
"Bubbles": "Пузыри",
|
||||
"Chat Width (PC)": "Ширина чата (на PC):",
|
||||
"No Blur Effect": "Отключить эффект размытия",
|
||||
"No Text Shadows": "Отключить тень текста",
|
||||
"Waifu Mode": "!!!РЕЖИМ ВАЙФУ!!!",
|
||||
"Message Timer": "Таймер сообщений",
|
||||
"Characters Hotswap": "Смена персонажей на лету",
|
||||
"Movable UI Panels": "Перемещение панелей интерфейса",
|
||||
"Reset Panels": "Сбросить панели",
|
||||
"UI Colors": "Цвета интерфейса",
|
||||
"Main Text": "Основной текст",
|
||||
"Italics Text": "Курсивный текст",
|
||||
"Quote Text": "Текст в кавычках",
|
||||
"Shadow Color": "Цвет теней",
|
||||
"FastUI BG": "Фон FastUI",
|
||||
"Blur Tint": "Оттенок размытия",
|
||||
"Font Scale": "Размер текста",
|
||||
"Blur Strength": "Сила размытия",
|
||||
"Text Shadow Width": "Размер теней текста",
|
||||
"UI Theme Preset": "Предустановки интерфейса",
|
||||
"Power User Options": "Продвинутые параметры",
|
||||
"Swipes": "Свайвы",
|
||||
"Background Sound Only": "Только фоновый звук",
|
||||
"Auto-load Last Chat": "Автоматически загружать последий чат",
|
||||
"Auto-save Message Edits": "Автоматически сохранять отредактированные сообщения",
|
||||
"Auto-fix Markdown": "Автоматическое исправление подчеркиваний",
|
||||
"Allow : in bot messages": "Разрешить : в сообщениях ИИ",
|
||||
"Auto-scroll Chat": "Автоматическая прокрутка чата",
|
||||
"Render Formulas": "Рендер формул",
|
||||
"Send on Enter": "Отправка на Enter",
|
||||
"Always disabled": "Всегда выключена",
|
||||
"Automatic (desktop)": "Автоматически (системные настройки)",
|
||||
"Always enabled": "Всегда включена",
|
||||
"Name": "Имя",
|
||||
"Your Avatar": "Ваш Аватар",
|
||||
"Extensions API:": "API для расширений",
|
||||
"SillyTavern-extras": "SillyTavern-extras",
|
||||
"Auto-connect": "Автоматическое соединение",
|
||||
"Active extensions": "Актививные расширения",
|
||||
"Extension settings": "Настройки расширений",
|
||||
"Description": "Описание",
|
||||
"First message": "Первое сообщение",
|
||||
"Group Controls": "Контроль группы",
|
||||
"Group reply strategy": "Сортировка ответов в группе",
|
||||
"Natural order": "Обычный порядок",
|
||||
"List order": "Порядок по листу",
|
||||
"Allow self responses": "Разрешить ответ себе",
|
||||
"Auto Mode": "Автоматический режим",
|
||||
"Add Members": "Добавить членов",
|
||||
"Current Members": "Текущие члены",
|
||||
"text": "текст",
|
||||
"Delete": "Удалить",
|
||||
"Cancel": "Отменить",
|
||||
"Advanced Defininitions": "Улучшенная узнаваемость",
|
||||
"Personality summary": "Личная сводка",
|
||||
"A brief description of the personality": "Краткое описание личности",
|
||||
"Scenario": "Сценарий",
|
||||
"Circumstances and context of the dialogue": "Обстоятельства и контекст диалога",
|
||||
"Talkativeness": "Разговорчивость",
|
||||
"How often the chracter speaks in": "Как часто персонаж говорит",
|
||||
"group chats!": "в груповых чатах",
|
||||
"Shy": "Застенчивый",
|
||||
"Normal": "Обычный",
|
||||
"Chatty": "Разговорчивый",
|
||||
"Examples of dialogue": "Примеры диалога",
|
||||
"Forms a personality more clearly": "Определите личность более точно",
|
||||
"Save": "Сохранить",
|
||||
"World Info Editor": "Редактирование информации о мире",
|
||||
"New summary": "Новая переменная",
|
||||
"Export": "Экспорт",
|
||||
"Delete World": "Удалить мир",
|
||||
"Chat History": "История чата",
|
||||
"Group Chat Scenario Override": "Замещение сценария группового чата",
|
||||
"All group members will use the following scenario text instead of what is specified in their character cards.": "Все участники группы будут использовать следующий сценарий, вместо обозначенного в карточках персонажей",
|
||||
"Keywords": "Ключевые слова",
|
||||
"Separate with commas": "Разделять запятыми",
|
||||
"Secondary Required Keywords": "Вторичные ключевые слова",
|
||||
"Content": "Содержание",
|
||||
"What this keyword should mean to the AI": "Значение данного ключевого слова для ИИ",
|
||||
"Memo/Note": "Напоминание",
|
||||
"Not sent to AI": "Не отправлять ИИ",
|
||||
"Constant": "Постоянно",
|
||||
"Selective": "Выборочно",
|
||||
"Before Char": "Перед Персонажем",
|
||||
"After Char": "После Персонажа",
|
||||
"Insertion Order": "Порядок внесения",
|
||||
"Tokens:": "Токены",
|
||||
"Disable": "Отключено",
|
||||
"${characterName}": "${имяПерсонажа}",
|
||||
"CHAR": "ПЕРСОНАЖ",
|
||||
"is typing": "Печатает...",
|
||||
"Back to parent chat": "Вернуться в основной чат",
|
||||
"Save bookmark": "Сохранить закладку",
|
||||
"Convert to group": "Превратить в группу",
|
||||
"Start new chat": "Начать новый чат",
|
||||
"View past chats": "Посмотреть прошлые чаты",
|
||||
"Delete messages": "Удалить сообщения",
|
||||
"Impersonate": "Перевоплощение",
|
||||
"Regenerate": "Повторная генерация",
|
||||
"PNG": "PNG",
|
||||
"JSON": "JSON",
|
||||
"WEBP": "WEBP",
|
||||
"presets": "Предустановки",
|
||||
"Message Sound": "Звук сообщения",
|
||||
"Author's Note": "Авторские заметки",
|
||||
"Send Jailbreak": "Отправлять JailBreak",
|
||||
"Replace empty message": "Заменять пустые сообщения",
|
||||
"Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
|
||||
"NSFW avoidance prompt": "Инструкции для избегания NSFW",
|
||||
"Prompt that is used when the NSFW toggle is off": "Инструкции, используемые, когда поощрение NSFW отключено",
|
||||
"Advanced prompt bits": "Дополнительные инструкции",
|
||||
"World Info format": "Шаблон форматирования Информации о мире",
|
||||
"Wraps activated World Info entries before inserting into the prompt. Use {0} to mark a place where the content is inserted.": "Выделяет активную Информацию о мире перед внесением в инструкции. Используйте {0} чтобы уточнить место с необходимой информацией.",
|
||||
"Unrestricted maximum value for the context slider": "Неограниченное максимальное значение для ползунка с размером контекста",
|
||||
"Chat Completion Source": "Источник для Chat Completion",
|
||||
"Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde",
|
||||
"Review the Privacy statement": "Посмотреть Privacy statement",
|
||||
"Learn how to contribute your idel GPU cycles to the Horde": "Изучите, как использовать GPU в состоянии простоя на благо Horde",
|
||||
"Trusted workers only": "Только доверенные рабочие",
|
||||
"For privacy reasons, your API key will be hidden after you reload the page.": "По причинам безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
|
||||
"-- Horde models not loaded --": "--Модель Horde не загружена--",
|
||||
"Example: http://127.0.0.1:5000/api ": "Пример: http://127.0.0.1:5000/api",
|
||||
"No connection...": "Нет соединения...",
|
||||
"Get your NovelAI API Key": "Получите свой API-ключ для NovelAI",
|
||||
"KoboldAI Horde": "KoboldAI Horde",
|
||||
"Text Gen WebUI (ooba)": "Text Gen WebUI (ooba)",
|
||||
"NovelAI": "NovelAI",
|
||||
"Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale)": "Дополнение диалога (OpenAI, Claude, Window/OpenRouter, Scale)",
|
||||
"OpenAI API key": "API-ключ для OpenAI",
|
||||
"Trim spaces": "Обрезать пробелы",
|
||||
"Trim Incomplete Sentences": "Обрезать неоконченные предложения",
|
||||
"Include Newline": "Использовать красную строку",
|
||||
"Non-markdown strings": "Неподчеркиваемые Strings",
|
||||
"Replace Macro in Sequences": "Заменить макросы в последовательности",
|
||||
"Presets": "Предустановки",
|
||||
"Separator": "Разделитель",
|
||||
"Start Reply With": "Начинать ответ с",
|
||||
"Show reply prefix in chat": "Показывать префиксы ответов в чате",
|
||||
"Worlds/Lorebooks": "Миры/Сведения",
|
||||
"Active World(s)": "Активные миры",
|
||||
"Character Lore Insertion Strategy": "Порядок включения сведений",
|
||||
"Sorted Evenly": "Равномерная сортировка",
|
||||
"Character Lore First": "Сначала сведения о персонаже",
|
||||
"Global Lore First": "Сначала общие сведения",
|
||||
"-- World Info not found --": "Информация о Мире не найдена",
|
||||
"Recursive Scan": "Рекурсивное сканирование",
|
||||
"Case Sensitive": "Учитывать регистр",
|
||||
"Match whole words": "Только полное совпадение",
|
||||
"World/Lore Editor": "Редактировать Мир/Сведения",
|
||||
"--- None ---": "---Отсутствует---",
|
||||
"Comma seperated (ignored if empty)": "Разделение запятыми (не используется, если оставлено пустым)",
|
||||
"Use Probability": "Использовать вероятность",
|
||||
"Exclude from recursion": "Исключить из рекурсии",
|
||||
"Position:": "Положение:",
|
||||
"Before Char Defs": "Перед определением Персонажа",
|
||||
"After Char Defs": "После определения Персонажа",
|
||||
"Before AN": "Перед AN",
|
||||
"After AN": "После AN",
|
||||
"Order:": "Порядок:",
|
||||
"Probability:": "Вероятность:",
|
||||
"Delete Entry": "Удалить запись:",
|
||||
"User Message Blur Tint": "Оттенок размытия сообщения пользователя",
|
||||
"AI Message Blur Tint": "Оттенок размытия сообщения ИИ",
|
||||
"Chat Style:": "Стиль чата",
|
||||
"Chat Width (PC):": "Ширина чата (для ПК)",
|
||||
"Chat Timestamps": "Временные обозначения в чате",
|
||||
"Message IDs": "ID сообщений",
|
||||
"Prefer Character Card Prompt": "Предпочитать инструкции из Карточки Персонажа",
|
||||
"Prefer Character Card Jailbreak": "Предпочитать JailBreak из Карточки Персонажа",
|
||||
"Press Send to continue": "Нажатие Отправить для продолжения",
|
||||
"Log prompts to console": "Выводы журнала в консоли",
|
||||
"Never resize avatars": "Никогда не менять размер аватаров",
|
||||
"Show avatar filenames": "Показывать названия файлов аватаров",
|
||||
"Import Card Tags": "Импорт меток Карточки",
|
||||
"Confirm message deletion": "Подтверждение удаления сообщений",
|
||||
"Spoiler Free Mode": "Режим без спойлеров",
|
||||
"Auto-swipe": "Автоматические свайпы",
|
||||
"Minimum generated message length": "Минимальная длина сгенерированных сообщений",
|
||||
"Blacklisted words": "Запрещенные слова",
|
||||
"Blacklisted word count to swipe": "Количество запрещенных слов для свайпа",
|
||||
"Reload Chat": "Перезагрузить чат",
|
||||
"Not Connected": "Не подключено",
|
||||
"Persona Management": "Управление Персоной",
|
||||
"Persona Description": "Описание Персоны",
|
||||
"In Story String / Chat Completion: Before Character Card": "В строке истории / Дополнение диалога: Перед Карточкой Персонажа",
|
||||
"In Story String / Chat Completion: After Character Card": "В строке истории / Дополнение диалога: После Карточки Персонажа",
|
||||
"Top of Author's Note": "Перед Авторскими Заметками",
|
||||
"Bottom of Author's Note": "После Авторских Заметок",
|
||||
"How do I use this?": "Как мне это использовать?",
|
||||
"More...": "Узнать больше...",
|
||||
"Link to World Info": "Ссылка на информацию о мире",
|
||||
"Import Card Lore": "Импортировать Карточку Сведений",
|
||||
"Scenario Override": "Замещение сценария",
|
||||
"Rename": "Переименовать",
|
||||
"Character Description": "Описание персонажа",
|
||||
"Creator's Notes": "Заметки создателя",
|
||||
"A-Z": "A-Z",
|
||||
"Z-A": "Z-A",
|
||||
"Newest": "Новейшие",
|
||||
"Oldest": "Старейшие",
|
||||
"Favorites": "Любимые",
|
||||
"Recent": "Последние",
|
||||
"Most chats": "Больше всего чатов",
|
||||
"Least chats": "Меньше всего чатов",
|
||||
"Back": "Назад",
|
||||
"Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct mode)": "Замещение инструкций (Для OpenAI/Claude/Scale API, Window/OpenRouter, и Режима Instruct)",
|
||||
"Insert {{original}} into either box to include the respective default prompt from system settings.": "Внесите {{original}} в любое поле для внесения стандартных инструкций из системных настроек",
|
||||
"Main Prompt": "Главные инструкции",
|
||||
"Jailbreak": "JailBreak",
|
||||
"Creator's Metadata (Not sent with the AI prompt)": "Сведения о создателе (не отправляются ИИ с инструкциями)",
|
||||
"Everything here is optional": "Всё в данных полях опционально",
|
||||
"Created by": "Создано",
|
||||
"Character Version": "Версия Персонажа",
|
||||
"Tags to Embed": "Теги для встраивания",
|
||||
"How often the character speaks in group chats!": "Как часто персонаж говорит в групповых чатах",
|
||||
"Important to set the character's writing style.": "Важные замечания для стиля написания персонажа",
|
||||
"ATTENTION!": "ВНИМАНИЕ!",
|
||||
"Samplers Order": "Порядок семплирования",
|
||||
"Samplers will be applied in a top-down order. Use with caution.": "Семплирование будет применено в порядке сверху-вниз. Используйте с осторожностью.",
|
||||
"Repetition Penalty": "Наказание за повторы",
|
||||
"Epsilon Cutoff": "Epsilon Cutoff",
|
||||
"Eta Cutoff": "Eta Cutoff",
|
||||
"Rep. Pen. Range.": "Размер наказания за повторы",
|
||||
"Rep. Pen. Freq.": "Частота наказания за повторы",
|
||||
"Rep. Pen. Presence": "Наличие наказания за повторы",
|
||||
"Enter it in the box below:": "Введите в поле ниже:",
|
||||
"separate with commas w/o space between": "разделять запятыми без пробелов между:",
|
||||
"Document": "Документ",
|
||||
"Suggest replies": "Предлагать ответы",
|
||||
"Show suggested replies. Not all bots support this.": "Показывать предлагаемые ответы. Не все боты поддерживают это.",
|
||||
"Use 'Unlocked Context' to enable chunked generation.": "Использовать 'Безлимитный контекст' для активации кусочной генерации",
|
||||
"It extends the context window in exchange for reply generation speed.": "Увеличивает размер контекста в обмен на скорость генерации.",
|
||||
"Continue": "Пролдолжить",
|
||||
"Editing:": "Изменения",
|
||||
"AI reply prefix": "Префикс Ответ ИИ",
|
||||
"Custom Stopping Strings": "Настройка ограничивающий нитей",
|
||||
"JSON serialized array of strings": "JSON ориентированный набор нитей",
|
||||
"words you dont want generated separated by comma ','": "слова которые вы не хотите при генерации здесь, разделенные запятой",
|
||||
"Extensions URL": "URL расширений ",
|
||||
"API Key": "Ключ API",
|
||||
"Enter your name": "Введите свое имя",
|
||||
"Name this character": "Назовите этого персонажа",
|
||||
"Search / Create Tags": "Искать / Создать тэги",
|
||||
"Describe your character's physical and mental traits here.": "Опишите ментальные и физические черты персонажа здесь",
|
||||
"This will be the first message from the character that starts every chat.": "Это булет первое сообщение от Персонажа при начале нового чата",
|
||||
"Chat Name (Optional)": "Название Чата (Необязательно)",
|
||||
"Filter...": "Отфильтровать...",
|
||||
"Search...": "Поиск...",
|
||||
"Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Все содержание этой ячейки будет заменять стандартный Промт",
|
||||
"Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Все содержание этой ячейки будет заменять стандартный Джейлбрейк",
|
||||
"(Botmaker's name / Contact Info)": "Ваше имя / Контакты",
|
||||
"(If you want to track character versions)": "Если вы хотите отслеживать модель персонажа",
|
||||
"(Describe the bot, give use tips, or list the chat models it has been tested on. This will be displayed in the character list.)": "Опишите Персонажа, дайте советы, или упомяните на каких моделях он был протестирован",
|
||||
"(Write a comma-separated list of tags)": "Запишите лист тэгов, разделяя запятой",
|
||||
"(A brief description of the personality)": "Краткое описание личности)",
|
||||
"(Circumstances and context of the interaction)": "(Обстоятельства и контекст этого взаимодействия)",
|
||||
"(Examples of chat dialog. Begin each example with START on a new line.)": "(Примеры диалога. Начинайте каждый пример с START или новой линией.)",
|
||||
"Injection text (supports parameters)": "Текст включения (Поддерживает параметры)",
|
||||
"Injection depth": "Глубина включения",
|
||||
"Type here...": "Пишите здесь...",
|
||||
"Comma separated (required)": "Разделено запятыми (Обязательно)",
|
||||
"Comma separated (ignored if empty)": "Разделено запятыми (Игнорируется если пусто)",
|
||||
"What this keyword should mean to the AI, sent verbatim": "Значение этого ключевого слова, отправляется ИИ дословно",
|
||||
"Not sent to the AI": "Не отправляется ИИ",
|
||||
"(This will be the first message from the character that starts every chat)": "(Это будет первое сообщение от персонажа, когда вы начинаете новый чат)",
|
||||
"Not connected to API!": "Нет подключения к API",
|
||||
"AI Response Configuration": "Еастройка Ответа ИИ",
|
||||
"AI Configuration panel will stay open": "Панель Настройки ИИ останется открытой",
|
||||
"Update current preset": "Обновить текущую настройку",
|
||||
"Create new preset": "Создать новую настройку",
|
||||
"Import preset": "Внести настройку",
|
||||
"Export preset": "Скачать настройку",
|
||||
"Delete the preset": "Удалить настройку",
|
||||
"NSFW block goes first in the resulting prompt": "НСФВ блокировка идет первой при отправки Промта",
|
||||
"Enables OpenAI completion streaming": "Включить процесс генерации OpenAI",
|
||||
"Wrap user messages in quotes before sending": "Заключить ответ Пользователя в кавычки",
|
||||
"Restore default prompt": "Восстановить станндартный промт",
|
||||
"New preset": "Новая настройка",
|
||||
"Delete preset": "Удалить настройку",
|
||||
"Restore default jailbreak": "Восстановить стандартный Джейлбрейк",
|
||||
"Restore default reply": "Восстановить стандартный ответ",
|
||||
"Restore defaul note": "Восстановить стандартную заметку",
|
||||
"API Connections": "Соединения API",
|
||||
"Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь с плохими ответами ставя в очередь только подтвержденных работников. Может замедлить время ответа.",
|
||||
"Clear your API key": "Очистить свои ключи API",
|
||||
"Refresh models": "Обновить модели",
|
||||
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai",
|
||||
"Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Подверждает ваше соединение к API. Знайте, что за это снимут деньги с вашего счета.",
|
||||
"Create New": "Создать новое",
|
||||
"Edit": "Изменить",
|
||||
"World Info & Soft Prompts": "Информация о Мире & Мягкий Промт",
|
||||
"Locked = World Editor will stay open": "Закреплено = Редактирование Мира останется открытым",
|
||||
"Entries can activate other entries by mentioning their keywords": "Записи могут активировать другие записи если в них содержаться ключевые слова",
|
||||
"Lookup for the entry keys in the context will respect the case": "Большая буква имеет значение при активации ключевого слова",
|
||||
"If the entry key consists of only one word, it would not be matched as part of other words": "Если ключевое слово состоит только из одного слова, оно не будет активироваться как часть других слов",
|
||||
"Open all Entries": "Открыть все Записи",
|
||||
"Close all Entries": "Закрыть все Записи",
|
||||
"Create": "Создать",
|
||||
"Import World Info": "Внести Информацию Мира",
|
||||
"Export World Info": "Скачать Информацию Мира",
|
||||
"Delete World Info": "Удалить Информацию Мира",
|
||||
"Rename World Info": "Переименовать Информацию Мира",
|
||||
"Save changes to a new theme file": "Сохранить изменения в новой теме",
|
||||
"removes blur and uses alternative background color for divs": "убирает размытие и использует альтернативный фон для разделов",
|
||||
"If checked and the character card contains a prompt override (System Prompt), use that instead.": "Если выбрано и карточка персонажа содержит собственный промт (Системный Промт), выберите это",
|
||||
"If checked and the character card contains a jailbreak override (Post History Instruction), use that instead.": "Если выбрано и карточка персонажа содержит собственный Джейлбрейк (После Истории Инструкций), выберите это",
|
||||
"AI Response Formatting": "Формат ответа ИИ",
|
||||
"Change Background Image": "Изменить фон",
|
||||
"Extensions": "Расширения",
|
||||
"Click to set a new User Name": "Нажмите что бы выбрать новое имя Личности",
|
||||
"Click to lock your selected persona to the current chat. Click again to remove the lock.": "Нажмите что бы закрепить выьранную личность к текущему чату",
|
||||
"Click to set user name for all messages": "Нажмите что бы закрепить Личность для всех сообщений",
|
||||
"Create a dummy persona": "Создать болванку",
|
||||
"Character Management": "Управление Персонажами",
|
||||
"Locked = Character Management panel will stay open": "Закреплено = Панель Управление Персонажами останется открытой ",
|
||||
"Select/Create Characters": "Выбрать/Создать персонажа",
|
||||
"Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным и используется только для примера",
|
||||
"Click to select a new avatar for this character": "Нажмите что бы выбрать новый аватар для этого персонажа",
|
||||
"Add to Favorites": "Добавить в Любимые",
|
||||
"Advanced Definition": "Расширенные Определения",
|
||||
"Character Lore": "Сведения Персонажа",
|
||||
"Export and Download": "Экспортировать и Скачать",
|
||||
"Duplicate Character": "Дублировать персонажа",
|
||||
"Create Character": "Создать персонажа",
|
||||
"Delete Character": "Удалить персонажа",
|
||||
"View all tags": "Показать все тэги",
|
||||
"Click to set additional greeting messages": "Нажмите что бы создать дополнительное вступительное сообщение",
|
||||
"Show / Hide Description and First Message": "Показать/Убрать Описание и Первое сообщение",
|
||||
"Click to select a new avatar for this group": "Нажмите что бы выбрать новый аватар для этого группового чата",
|
||||
"Set a group chat scenario": "Создать сценарий для группового чата",
|
||||
"Restore collage avatar": "Восстановить коллаж персонажа",
|
||||
"Create New Character": "Создать нового персонажа",
|
||||
"Import Character from File": "Внести персонажа из файла",
|
||||
"Import content from external URL": "Вставить содержимое из внешнего URL",
|
||||
"Create New Chat Group": "Создать новый групповой чат",
|
||||
"Characters sorting order": "Упорядовачивание порядка персонажа",
|
||||
"Add chat injection": "Добавить включение в чат",
|
||||
"Remove injection": "Убрать включение",
|
||||
"Remove": "Убрать",
|
||||
"Select a World Info file for": "Выбрать файл для Информации Мира",
|
||||
"Primary Lorebook": "Основной Лорбук",
|
||||
"A selected World Info will be bound to this character as its own Lorebook.": "Информация Мира будет закреплена на персонажем как собственный Лорбук",
|
||||
"When generating an AI reply, it will be combined with the entries from a global World Info selector.": "Когда ИИ генерирует ответ, в него будет включены заметки из Информации Мира",
|
||||
"Exporting a character would also export the selected Lorebook file embedded in the JSON data.": "Скачивание этого персонажа так же будет включать в себя закрепленный за ним Сведения",
|
||||
"Additional Lorebooks": "Вспомогательные Сведения",
|
||||
"Associate one or more auxillary Lorebooks with this character.": "Закрепить этот или еще больше вспомогательных Сведений за этим персонажем",
|
||||
"NOTE: These choices are optional and won't be preserved on character export!": "ВНИМАНИЕ: Эти выборы необязательные и не будут сохранены при экспорте персонажа",
|
||||
"Rename chat file": "Переименовать чат",
|
||||
"Export JSONL chat file": "Создать чат в форме JSONL",
|
||||
"Download chat as plain text document": "Скачать чат в формте .txt",
|
||||
"Delete chat file": "Удалить файл этого чата",
|
||||
"Delete tag": "Удалить тэг",
|
||||
"Translate message": "Перевести сообщение",
|
||||
"Generate Image": "Создать изображение",
|
||||
"Narrate": "Повествовать",
|
||||
"Prompt": "Промт",
|
||||
"Create Bookmark": "Создать закладку",
|
||||
"Copy": "Скопировать",
|
||||
"Open bookmark chat": "Открыть чат из закладки",
|
||||
"Confirm": "Подтвердить",
|
||||
"Copy this message": "Скопировать это сообщение",
|
||||
"Delete this message": "Удалить это сообщение",
|
||||
"Move message up": "Переместить сообщение вверх",
|
||||
"Move message down": "Переместить сообщение вниз",
|
||||
"Enlarge": "Увеличить",
|
||||
"Temporarily disable automatic replies from this character": "Временно отключить сообщения от этого персонажа",
|
||||
"Enable automatic replies from this character": "Включить автоматическую отправку сообщения этого персонажа",
|
||||
"Trigger a message from this character": "Активировать сообщение этого персонажа",
|
||||
"Move up": "Переместить вверх",
|
||||
"Move down": "Переместить вниз",
|
||||
"View character card": "Посмотреть карточку персонажа",
|
||||
"Remove from group": "Убрать из группы",
|
||||
"Add to group": "Добавить в группу",
|
||||
"Add": "Добавить",
|
||||
"Abort request": "Прекратить генерацию",
|
||||
"Send a message": "отправить сообщение",
|
||||
"Ask AI to write your message for you": "ИИ напишет сообщение за вас",
|
||||
"Continue the last message": "Продолжить текущее сообщение",
|
||||
"Bind user name to that avatar": "Закрепить имя за этой личностью",
|
||||
"Select this as default persona for the new chats.": "Выбрать эту как стартовую личность",
|
||||
"Change persona image": "Сменить изображение личности",
|
||||
"Delete persona": "Удалить личность"
|
||||
}
|
||||
}
|
||||
|
20
public/img/ai21.svg
Normal file
20
public/img/ai21.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="" height="" viewBox="0 0 60 15" fill="none"
|
||||
data-inject-url="http://127.0.0.1:8001/img/ai21.svg" class="icon-svg timestamp-icon">
|
||||
<g clip-path="url(#clip0--inject-2)">
|
||||
<path
|
||||
d="M16.6207 23.4327L15.7407 20.6609H7.62075L6.74035 23.4327H1.10132L9.25303 0.609131H14.1115L22.361 23.4327H16.6207ZM11.6987 7.12926L8.89437 16.52H14.4377L11.6987 7.12926Z"
|
||||
fill=""></path>
|
||||
<path d="M22.9893 0.609131H28.3367V23.4327H22.9893V0.609131Z" fill=""></path>
|
||||
<path
|
||||
d="M29.778 18.7697C30.0181 17.6428 30.4422 16.5632 31.0333 15.5743C31.5474 14.7385 32.1914 13.9901 32.941 13.357C33.7068 12.7218 34.5187 12.1443 35.37 11.6294C36.0436 11.2378 36.6685 10.8627 37.2449 10.5043C37.7764 10.178 38.2832 9.81296 38.7611 9.4121C39.1738 9.06912 39.5217 8.65497 39.7883 8.18927C40.0429 7.72502 40.1721 7.20247 40.1632 6.67309C40.1632 5.7602 39.9133 5.10266 39.4134 4.70047C38.8833 4.2885 38.2258 4.07506 37.5548 4.09717C36.7912 4.07408 36.0486 4.34892 35.4841 4.86356C34.9298 5.37448 34.6527 6.2277 34.6527 7.42323H29.4028C29.3934 6.43009 29.5762 5.44452 29.9411 4.52079C30.291 3.64199 30.831 2.85144 31.5222 2.20575C32.2584 1.52916 33.1235 1.00796 34.0657 0.673329C35.1756 0.285645 36.3455 0.0978785 37.5209 0.118755C38.5297 0.114098 39.5341 0.25135 40.5046 0.526479C41.4073 0.777025 42.2539 1.19757 42.999 1.76555C43.731 2.33806 44.3175 3.07539 44.7107 3.9175C45.1549 4.89419 45.3723 5.95876 45.3465 7.0314C45.3582 7.93876 45.1454 8.83495 44.7269 9.64014C44.3262 10.406 43.8154 11.109 43.2108 11.7268C42.6204 12.3291 41.9705 12.87 41.271 13.3411C40.5746 13.8087 39.9441 14.2054 39.3795 14.5311C38.5964 15.0529 37.9496 15.504 37.4394 15.8845C37.0016 16.2011 36.5925 16.5556 36.2169 16.9439C35.9359 17.2322 35.7191 17.5768 35.5808 17.9549C35.4549 18.3444 35.3943 18.752 35.4015 19.1612H45.1837V23.4326H29.3378C29.3099 21.8667 29.4575 20.3027 29.778 18.7697Z"
|
||||
fill=""></path>
|
||||
<path
|
||||
d="M46.1938 4.61724C47.1075 4.63586 48.0205 4.55382 48.9163 4.37261C49.5146 4.25901 50.0784 4.00788 50.5631 3.63905C50.952 3.31635 51.2358 2.88492 51.3782 2.39998C51.5411 1.81652 51.618 1.21237 51.6062 0.606689H56.2708V23.4327H50.891V8.33548H46.1938V4.61724Z"
|
||||
fill=""></path>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0--inject-2">
|
||||
<rect width="117.818" height="24" fill="white" transform="translate(1.09106)"></rect>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
@@ -22,31 +22,45 @@
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="img/apple-icon-114x114.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="img/apple-icon-144x144.png" />
|
||||
|
||||
<script src="scripts/jquery-3.5.1.min.js"></script>
|
||||
<script src="scripts/jquery-ui.min.js"></script>
|
||||
<script src="scripts/jquery.transit.min.js"></script>
|
||||
<script src="scripts/jquery-cookie-1.4.1.min.js"></script>
|
||||
<script src="scripts/jquery.ui.touch-punch.min.js"></script>
|
||||
<script src="scripts/showdown.min.js"></script>
|
||||
<script src="scripts/showdown-katex.min.js"></script>
|
||||
<script src="scripts/popper.js"></script>
|
||||
<script src="scripts/purify.min.js"></script>
|
||||
<script src="scripts/highlight.min.js"></script>
|
||||
<script src="scripts/moment.min.js"></script>
|
||||
<script src="scripts/cropper.min.js"></script>
|
||||
<script src="scripts/jquery-cropper.min.js"></script>
|
||||
<script src="scripts/toastr.min.js"></script>
|
||||
<script src="scripts/fuse.js"></script>
|
||||
<script src="scripts/select2.min.js"></script>
|
||||
<script src="scripts/seedrandom.min.js"></script>
|
||||
<script src="scripts/droll.js"></script>
|
||||
<script src="scripts/localforage.min.js"></script>
|
||||
<script src="scripts/handlebars.js"></script>
|
||||
<script src="scripts/pagination.js"></script>
|
||||
<script type="module" src="scripts/eventemitter.js"></script>
|
||||
<script src="lib/jquery-3.5.1.min.js"></script>
|
||||
<script src="lib/jquery-ui.min.js"></script>
|
||||
<script src="lib/jquery.transit.min.js"></script>
|
||||
<script src="lib/jquery-cookie-1.4.1.min.js"></script>
|
||||
<script src="lib/jquery.ui.touch-punch.min.js"></script>
|
||||
<script src="lib/showdown.min.js"></script>
|
||||
<script src="lib/showdown-katex.min.js"></script>
|
||||
<script src="lib/popper.js"></script>
|
||||
<script src="lib/purify.min.js"></script>
|
||||
<script src="lib/highlight.min.js"></script>
|
||||
<script src="lib/moment.min.js"></script>
|
||||
<script src="lib/cropper.min.js"></script>
|
||||
<script src="lib/jquery-cropper.min.js"></script>
|
||||
<script src="lib/toastr.min.js"></script>
|
||||
<script src="lib/fuse.js"></script>
|
||||
<script src="lib/select2.min.js"></script>
|
||||
<script src="lib/seedrandom.min.js"></script>
|
||||
<script src="lib/droll.js"></script>
|
||||
<script src="lib/localforage.min.js"></script>
|
||||
<script src="lib/handlebars.js"></script>
|
||||
<script src="lib/pagination.js"></script>
|
||||
<script src="lib/toolcool-color-picker.js"></script>
|
||||
<script src="lib/svg-inject.js"></script>
|
||||
<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>
|
||||
<script type="module" src="scripts/swiped-events.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>
|
||||
@@ -100,7 +114,6 @@
|
||||
<script type="module" src="scripts/authors-note.js"></script>
|
||||
<script type="module" src="scripts/preset-manager.js"></script>
|
||||
<script type="module" src="scripts/filters.js"></script>
|
||||
<script type="text/javascript" src="scripts/toolcool-color-picker.js"></script>
|
||||
|
||||
<title>SillyTavern</title>
|
||||
</head>
|
||||
@@ -130,13 +143,12 @@
|
||||
<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">
|
||||
<div class="flex-container" id="ai_response_configuration">
|
||||
<div id="respective-presets-block" class="width100p">
|
||||
<input type="file" hidden data-preset-manager-file="" accept=".json, .settings">
|
||||
<div id="kobold_api-presets">
|
||||
<h3><span data-i18n="kobldpresets">Kobold Presets</span>
|
||||
<a href="https://docs.sillytavern.app/usage/api-connections/koboldai/" class="notes-link" target="_blank">
|
||||
@@ -148,10 +160,11 @@
|
||||
<select id="settings_perset" data-preset-manager-for="kobold">
|
||||
<option value="gui" data-i18n="guikoboldaisettings">GUI KoboldAI Settings</option>
|
||||
</select>
|
||||
<input type="file" hidden data-preset-manager-file="kobold" accept=".json, .settings">
|
||||
<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>
|
||||
@@ -166,10 +179,11 @@
|
||||
<select id="settings_perset_novel" data-preset-manager-for="novel">
|
||||
<option value="gui" data-i18n="default">Default</option>
|
||||
</select>
|
||||
<input type="file" hidden data-preset-manager-file="novel" accept=".json, .settings">
|
||||
<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>
|
||||
@@ -182,8 +196,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>
|
||||
@@ -195,10 +209,11 @@
|
||||
<div class="preset_buttons">
|
||||
<select id="settings_preset_textgenerationwebui" data-preset-manager-for="textgenerationwebui">
|
||||
</select>
|
||||
<input type="file" hidden data-preset-manager-file="textgenerationwebui" accept=".json, .settings">
|
||||
<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>
|
||||
@@ -654,7 +669,7 @@
|
||||
Max prompt cost: <span id="openrouter_max_prompt_cost">Unknown</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter">
|
||||
<div class="range-block" data-source="openai,claude,windowai,openrouter,ai21">
|
||||
<div class="range-block-title" data-i18n="Temperature">
|
||||
Temperature
|
||||
</div>
|
||||
@@ -669,7 +684,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter">
|
||||
<div class="range-block" data-source="openai,openrouter,ai21">
|
||||
<div class="range-block-title" data-i18n="Frequency Penalty">
|
||||
Frequency Penalty
|
||||
</div>
|
||||
@@ -684,7 +699,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,openrouter">
|
||||
<div class="range-block" data-source="openai,openrouter,ai21">
|
||||
<div class="range-block-title" data-i18n="Presence Penalty">
|
||||
Presence Penalty
|
||||
</div>
|
||||
@@ -699,7 +714,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude,openrouter">
|
||||
<div class="range-block" data-source="ai21">
|
||||
<div class="range-block-title" data-i18n="Count Penalty">
|
||||
Count Penalty
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="count_pen" name="volume" min="0" max="1" step="0.01">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="count_pen" id="count_pen_counter">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude,openrouter,ai21">
|
||||
<div class="range-block-title" data-i18n="Top K">
|
||||
Top K
|
||||
</div>
|
||||
@@ -714,7 +744,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude,openrouter">
|
||||
<div class="range-block" data-source="openai,claude,openrouter,ai21">
|
||||
<div class="range-block-title" data-i18n="Top-p">
|
||||
Top P
|
||||
</div>
|
||||
@@ -1418,6 +1448,22 @@
|
||||
<span data-i18n="May help the model to understand context. Names must only contain letters or numbers.">Helps the model to associate messages in group chats. Names must only contain letters or numbers without whitespaces.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="ai21">
|
||||
<label for="use_ai21_tokenizer" title="Use AI21 Tokenizer" class="checkbox_label widthFreeExpand">
|
||||
<input id="use_ai21_tokenizer" type="checkbox" /><span data-i18n="Use AI21 Tokenizer">Use AI21 Tokenizer</span>
|
||||
</label>
|
||||
<div class="toggle-description justifyLeft">
|
||||
<span data-i18n="Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.">Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude">
|
||||
<label for="exclude_assistant" title="Exclude Assistant suffix" class="checkbox_label widthFreeExpand">
|
||||
<input id="exclude_assistant" type="checkbox" /><span data-i18n="Exclude Assistant suffix">Exclude Assistant suffix</span>
|
||||
</label>
|
||||
<div class="toggle-description justifyLeft">
|
||||
<span data-i18n="Exclude the assistant suffix from being added to the end of prompt.">Exclude the assistant suffix from being added to the end of prompt (Requires jailbreak with 'Assistant:' in it).</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer m-t-1 wide100p">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b data-i18n="Quick Edit">Quick Edit</b>
|
||||
@@ -1429,8 +1475,8 @@
|
||||
<span data-i18n="Select a character to show quick edit options.">Select a character to show quick edit options.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude">
|
||||
<span data-i18n="Assistant Prefill">Assistant Prefill</span>
|
||||
<div id="claude_assistant_prefill_block" data-source="claude" class="range-block">
|
||||
<span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span>
|
||||
<textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill" rows="3" maxlength="5000" placeholder="Start Claude's answer with..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1577,8 +1623,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>
|
||||
@@ -1618,7 +1664,7 @@
|
||||
<option value="koboldhorde"><span data-i18n="KoboldAI Horde">KoboldAI Horde</span></option>
|
||||
<option value="textgenerationwebui"><span data-i18n="Text Gen WebUI (ooba/Mancer)">Text Gen WebUI (ooba/Mancer)</span></option>
|
||||
<option value="novel"><span data-i18n="NovelAI">NovelAI</span></option>
|
||||
<option value="openai"><span data-i18n="Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale)">Chat Completion (OpenAI, Claude, Window, OpenRouter, Scale)</span></option>
|
||||
<option value="openai"><span data-i18n="Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale, AI21)">Chat Completion (OpenAI, Claude, Window, OpenRouter, Scale, AI21)</span></option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="kobold_horde" style="position: relative;"> <!-- shows the kobold settings -->
|
||||
@@ -1761,22 +1807,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">
|
||||
@@ -1804,6 +1855,7 @@
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="claude">Claude</option>
|
||||
<option value="scale">Scale</option>
|
||||
<option value="ai21">AI21</option>
|
||||
</select>
|
||||
<form id="openai_form" data-source="openai" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4><span data-i18n="OpenAI API key">OpenAI API key</span></h4>
|
||||
@@ -1972,6 +2024,27 @@
|
||||
<select id="model_scale_select" class="displayNone"></select>
|
||||
</form>
|
||||
|
||||
<form id="ai21_form" data-source="ai21" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4>AI21 API Key</h4>
|
||||
<div class="flex-container">
|
||||
<input id="api_key_ai21" name="api_key_ai21" class="text_pole flex1" maxlength="500" value="" 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_ai21"></div>
|
||||
</div>
|
||||
<div data-for="api_key_ai21" class="neutral_warning">
|
||||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
<div>
|
||||
<h4 data-i18n="AI21 Model">AI21 Model</h4>
|
||||
<select id="model_ai21_select">
|
||||
<optgroup label="Latest">
|
||||
<option value="j2-ultra">j2-ultra</option>
|
||||
<option value="j2-mid">j2-mid</option>
|
||||
<option value="j2-light">j2-light</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="flex-container flex">
|
||||
<input id="api_button_openai" class="menu_button" type="submit" value="Connect">
|
||||
<input data-source="openrouter" id="openrouter_authorize" class="menu_button" type="button" value="Authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">
|
||||
@@ -2015,14 +2088,14 @@
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="context_story_string" data-i18n="Story String">
|
||||
Story String
|
||||
<label for="context_story_string">
|
||||
<small data-i18n="Story String">Story String</small>
|
||||
</label>
|
||||
<textarea id="context_story_string" class="text_pole textarea_compact" rows="3"></textarea>
|
||||
<div class="flex-container">
|
||||
<div class="flex1">
|
||||
<label for="context_example_separator">
|
||||
<span data-i18n="Example Separator">Example Separator</span>
|
||||
<small data-i18n="Example Separator">Example Separator</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="context_example_separator" class="text_pole textarea_compact" maxlength="500" rows="1"></textarea>
|
||||
@@ -2030,7 +2103,7 @@
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="context_chat_start">
|
||||
<span data-i18n="Chat Start">Chat Start</span>
|
||||
<small data-i18n="Chat Start">Chat Start</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="context_chat_start" class="text_pole textarea_compact" maxlength="500" rows="1"></textarea>
|
||||
@@ -2070,9 +2143,26 @@
|
||||
<label for="instruct_presets">
|
||||
<span data-i18n="Presets">Presets</span>
|
||||
</label>
|
||||
<select id="instruct_presets"></select>
|
||||
<div class="preset_buttons">
|
||||
<select id="instruct_presets" data-preset-manager-for="instruct" class="flex1"></select>
|
||||
<input type="file" hidden data-preset-manager-file="instruct" accept=".json, .settings">
|
||||
<i id="instruct_set_default" class="menu_button fa-solid fa-heart" title="Auto-select this preset on API connection."></i>
|
||||
<i data-preset-manager-update="instruct" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i data-preset-manager-new="instruct" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i data-preset-manager-import="instruct" class="menu_button fa-solid fa-file-import" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
<i data-preset-manager-export="instruct" class="menu_button fa-solid fa-file-export" title="Export preset" data-i18n="[title]Export preset"></i>
|
||||
<i data-preset-manager-delete="instruct" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
|
||||
</div>
|
||||
<label>
|
||||
<span data-i18n="System Prompt">System Prompt</span>
|
||||
<small data-i18n="Activation Regex">
|
||||
Activation Regex
|
||||
</small>
|
||||
</label>
|
||||
<div>
|
||||
<textarea id="instruct_activation_regex" class="text_pole textarea_compact" maxlength="5000" rows="1"></textarea>
|
||||
</div>
|
||||
<label>
|
||||
<small data-i18n="System Prompt">System Prompt</small>
|
||||
</label>
|
||||
<div class="prompt_overridden">
|
||||
Overridden by the Character Definitions.
|
||||
@@ -2084,7 +2174,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">
|
||||
@@ -2092,7 +2182,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">
|
||||
@@ -2100,7 +2190,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>
|
||||
@@ -2110,7 +2200,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">
|
||||
@@ -2118,7 +2208,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">
|
||||
@@ -2126,7 +2216,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>
|
||||
@@ -2180,6 +2270,10 @@
|
||||
Keep Example Messages in Prompt
|
||||
</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="remove-examples-checkbox">
|
||||
<input id="remove-examples-checkbox" type="checkbox" />
|
||||
Strip Example Messages from Prompt
|
||||
</label>
|
||||
<label class="checkbox_label" for="collapse-newlines-checkbox"><input id="collapse-newlines-checkbox" type="checkbox" />
|
||||
<span data-i18n="Remove Empty New Lines from Output">
|
||||
Remove Empty New Lines from Output
|
||||
@@ -2224,7 +2318,7 @@
|
||||
</div>
|
||||
</h4>
|
||||
<div>
|
||||
<textarea id="custom_stopping_strings" rows="2" class="text_pole textarea_compact" placeholder="["Ford", "BMW", "Fiat"]"></textarea>
|
||||
<textarea id="custom_stopping_strings" rows="2" class="text_pole textarea_compact monospace" placeholder="["Ford", "BMW", "Fiat"]"></textarea>
|
||||
</div>
|
||||
<label class="checkbox_label" for="custom_stopping_strings_macro">
|
||||
<input id="custom_stopping_strings_macro" type="checkbox" checked>
|
||||
@@ -2917,8 +3011,8 @@
|
||||
<textarea id="persona_description" name="persona_description" placeholder="Example: [{{user}} is a 28-year-old Romanian cat girl.]" class="text_pole textarea_compact" maxlength="5000" value="" autocomplete="off" rows="4"></textarea>
|
||||
<label for="persona_description_position" data-i18n="Position:">Position:</label>
|
||||
<select id="persona_description_position">
|
||||
<option value="0" data-i18n="Before Character Card">Before Character Card</option>
|
||||
<option value="1" data-i18n="After Character Card">After Character Card</option>
|
||||
<option value="0" data-i18n="In Story String / Chat Completion: Before Character Card">In Story String / Chat Completion: Before Character Card</option>
|
||||
<option value="1" data-i18n="In Story String / Chat Completion: After Character Card">In Story String / Chat Completion: After Character Card</option>
|
||||
<option value="2" data-i18n="Top of Author's Note">Top of Author's Note</option>
|
||||
<option value="3" data-i18n="Bottom of Author's Note">Bottom of Author's Note</option>
|
||||
</select>
|
||||
@@ -3214,7 +3308,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>
|
||||
@@ -3238,7 +3332,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" />
|
||||
@@ -3267,16 +3361,6 @@
|
||||
</div>
|
||||
<div id="rm_print_characters_block" class="flexFlowColumn"></div>
|
||||
</div>
|
||||
|
||||
<!-- only displays after creating/deleting a character/group -->
|
||||
<div id="rm_info_block" class="right_menu">
|
||||
<div id="rm_info_panel">
|
||||
<div id="rm_info_avatar"></div>
|
||||
<div id="rm_info_text"></div>
|
||||
<div id="rm_info_button" class="menu_button" data-i18n="Back">Back</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -3403,8 +3487,9 @@
|
||||
|
||||
<div id="shadow_select_chat_popup">
|
||||
<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>
|
||||
@@ -3645,9 +3730,7 @@
|
||||
<small>
|
||||
<span data-i18n="Content">
|
||||
Content
|
||||
<span>(Tokens:
|
||||
<span class="world_entry_form_token_counter">0</span>
|
||||
)
|
||||
<span>(Tokens: <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)
|
||||
</span>
|
||||
</span>
|
||||
</small>
|
||||
|
@@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Alpaca",
|
||||
"system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "### Instruction:",
|
||||
"output_sequence": "### Response:",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Koala",
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION: ",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "GPT: ",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION: ",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": false
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Llama 2",
|
||||
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.\n<</SYS>>\n",
|
||||
"system_prompt": "[INST] <<SYS>>\nWrite {{char}}'s next reply in this fictional roleplay with {{user}}.\n<</SYS>>\n",
|
||||
"input_sequence": "[INST] ",
|
||||
"output_sequence": " [/INST] ",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "[INST] <<SYS>>\n",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "[INST]",
|
||||
"output_sequence": "[/INST]",
|
||||
"last_output_sequence": "",
|
||||
"separator_sequence": "\n",
|
||||
"wrap": false
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Metharme",
|
||||
"system_prompt": "Enter roleplay mode. You must act as {{char}}, whose persona follows:",
|
||||
"system_sequence": "<|system|>",
|
||||
"stop_sequence": "</s>",
|
||||
"input_sequence": "<|user|>",
|
||||
"output_sequence": "<|model|>",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "<|system|>",
|
||||
"stop_sequence": "</s>",
|
||||
"separator_sequence": "",
|
||||
"wrap": false
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"input_sequence": "User: ",
|
||||
"macro": true,
|
||||
"name": "OpenOrca/OpenChat",
|
||||
"names": true,
|
||||
"system_prompt": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate.\n",
|
||||
"input_sequence": "User: ",
|
||||
"output_sequence": "<|end_of_turn|>\nAssistant: ",
|
||||
"last_output_sequence": "",
|
||||
"separator_sequence": "<|end_of_turn|>\n",
|
||||
"stop_sequence": "",
|
||||
"system_prompt": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate.\n",
|
||||
"system_sequence": "",
|
||||
"wrap": false
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "<|end_of_turn|>\n",
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"input_sequence": "### Instruction:",
|
||||
"macro": true,
|
||||
"name": "Roleplay",
|
||||
"names": true,
|
||||
"last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
"output_sequence": "### Response:",
|
||||
"separator_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n\n### Input:",
|
||||
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n",
|
||||
"input_sequence": "\n### Instruction:",
|
||||
"output_sequence": "\n### Response:",
|
||||
"last_output_sequence": "\n### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
|
||||
"system_sequence": "",
|
||||
"wrap": true
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": true,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Vicuna 1.0",
|
||||
"system_prompt": "A chat between a curious human and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the human's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "### Human:",
|
||||
"output_sequence": "### Assistant:",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "Vicuna 1.1",
|
||||
"system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION:",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "ASSISTANT: ",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "BEGINNING OF CONVERSATION:",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": false
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "WizardLM-13B",
|
||||
"system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next detailed reply in a fictional roleplay chat between {{user}} and {{char}}.",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "USER: ",
|
||||
"output_sequence": "ASSISTANT: ",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
@@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "WizardLM",
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "",
|
||||
"output_sequence": "### Response:",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "</s>",
|
||||
"wrap": true
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
||||
|
15
public/instruct/simple-proxy-for-tavern.json
Normal file
15
public/instruct/simple-proxy-for-tavern.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "simple-proxy-for-tavern",
|
||||
"system_prompt": "[System note: Write one reply only. Do not decide what {{user}} says or does. Write at least one paragraph, up to four. Be descriptive and immersive, providing vivid details about {{char}}'s actions, emotions, and the environment. Write with a high degree of complexity and burstiness. Do not repeat this message.]",
|
||||
"input_sequence": "### Instruction:\n#### {{user}}:",
|
||||
"output_sequence": "### Response:\n#### {{char}}:",
|
||||
"last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):\n#### {{char}}:",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": true,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": false,
|
||||
"activation_regex": ""
|
||||
}
|
697
public/lib/svg-inject.js
Normal file
697
public/lib/svg-inject.js
Normal file
@@ -0,0 +1,697 @@
|
||||
/**
|
||||
* SVGInject - Version 1.2.3
|
||||
* A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM.
|
||||
*
|
||||
* https://github.com/iconfu/svg-inject
|
||||
*
|
||||
* Copyright (c) 2018 INCORS, the creators of iconfu.com
|
||||
* @license MIT License - https://github.com/iconfu/svg-inject/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
(function(window, document) {
|
||||
// constants for better minification
|
||||
var _CREATE_ELEMENT_ = 'createElement';
|
||||
var _GET_ELEMENTS_BY_TAG_NAME_ = 'getElementsByTagName';
|
||||
var _LENGTH_ = 'length';
|
||||
var _STYLE_ = 'style';
|
||||
var _TITLE_ = 'title';
|
||||
var _UNDEFINED_ = 'undefined';
|
||||
var _SET_ATTRIBUTE_ = 'setAttribute';
|
||||
var _GET_ATTRIBUTE_ = 'getAttribute';
|
||||
|
||||
var NULL = null;
|
||||
|
||||
// constants
|
||||
var __SVGINJECT = '__svgInject';
|
||||
var ID_SUFFIX = '--inject-';
|
||||
var ID_SUFFIX_REGEX = new RegExp(ID_SUFFIX + '\\d+', "g");
|
||||
var LOAD_FAIL = 'LOAD_FAIL';
|
||||
var SVG_NOT_SUPPORTED = 'SVG_NOT_SUPPORTED';
|
||||
var SVG_INVALID = 'SVG_INVALID';
|
||||
var ATTRIBUTE_EXCLUSION_NAMES = ['src', 'alt', 'onload', 'onerror'];
|
||||
var A_ELEMENT = document[_CREATE_ELEMENT_]('a');
|
||||
var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_;
|
||||
var DEFAULT_OPTIONS = {
|
||||
useCache: true,
|
||||
copyAttributes: true,
|
||||
makeIdsUnique: true
|
||||
};
|
||||
// Map of IRI referenceable tag names to properties that can reference them. This is defined in
|
||||
// https://www.w3.org/TR/SVG11/linking.html#processingIRI
|
||||
var IRI_TAG_PROPERTIES_MAP = {
|
||||
clipPath: ['clip-path'],
|
||||
'color-profile': NULL,
|
||||
cursor: NULL,
|
||||
filter: NULL,
|
||||
linearGradient: ['fill', 'stroke'],
|
||||
marker: ['marker', 'marker-end', 'marker-mid', 'marker-start'],
|
||||
mask: NULL,
|
||||
pattern: ['fill', 'stroke'],
|
||||
radialGradient: ['fill', 'stroke']
|
||||
};
|
||||
var INJECTED = 1;
|
||||
var FAIL = 2;
|
||||
|
||||
var uniqueIdCounter = 1;
|
||||
var xmlSerializer;
|
||||
var domParser;
|
||||
|
||||
|
||||
// creates an SVG document from an SVG string
|
||||
function svgStringToSvgDoc(svgStr) {
|
||||
domParser = domParser || new DOMParser();
|
||||
return domParser.parseFromString(svgStr, 'text/xml');
|
||||
}
|
||||
|
||||
|
||||
// searializes an SVG element to an SVG string
|
||||
function svgElemToSvgString(svgElement) {
|
||||
xmlSerializer = xmlSerializer || new XMLSerializer();
|
||||
return xmlSerializer.serializeToString(svgElement);
|
||||
}
|
||||
|
||||
|
||||
// Returns the absolute url for the specified url
|
||||
function getAbsoluteUrl(url) {
|
||||
A_ELEMENT.href = url;
|
||||
return A_ELEMENT.href;
|
||||
}
|
||||
|
||||
|
||||
// Load svg with an XHR request
|
||||
function loadSvg(url, callback, errorCallback) {
|
||||
if (url) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.onreadystatechange = function() {
|
||||
if (req.readyState == 4) {
|
||||
// readyState is DONE
|
||||
var status = req.status;
|
||||
if (status == 200) {
|
||||
// request status is OK
|
||||
callback(req.responseXML, req.responseText.trim());
|
||||
} else if (status >= 400) {
|
||||
// request status is error (4xx or 5xx)
|
||||
errorCallback();
|
||||
} else if (status == 0) {
|
||||
// request status 0 can indicate a failed cross-domain call
|
||||
errorCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
req.open('GET', url, true);
|
||||
req.send();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Copy attributes from img element to svg element
|
||||
function copyAttributes(imgElem, svgElem) {
|
||||
var attribute;
|
||||
var attributeName;
|
||||
var attributeValue;
|
||||
var attributes = imgElem.attributes;
|
||||
for (var i = 0; i < attributes[_LENGTH_]; i++) {
|
||||
attribute = attributes[i];
|
||||
attributeName = attribute.name;
|
||||
// Only copy attributes not explicitly excluded from copying
|
||||
if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) {
|
||||
attributeValue = attribute.value;
|
||||
// If img attribute is "title", insert a title element into SVG element
|
||||
if (attributeName == _TITLE_) {
|
||||
var titleElem;
|
||||
var firstElementChild = svgElem.firstElementChild;
|
||||
if (firstElementChild && firstElementChild.localName.toLowerCase() == _TITLE_) {
|
||||
// If the SVG element's first child is a title element, keep it as the title element
|
||||
titleElem = firstElementChild;
|
||||
} else {
|
||||
// If the SVG element's first child element is not a title element, create a new title
|
||||
// ele,emt and set it as the first child
|
||||
titleElem = document[_CREATE_ELEMENT_ + 'NS']('http://www.w3.org/2000/svg', _TITLE_);
|
||||
svgElem.insertBefore(titleElem, firstElementChild);
|
||||
}
|
||||
// Set new title content
|
||||
titleElem.textContent = attributeValue;
|
||||
} else {
|
||||
// Set img attribute to svg element
|
||||
svgElem[_SET_ATTRIBUTE_](attributeName, attributeValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This function appends a suffix to IDs of referenced elements in the <defs> in order to to avoid ID collision
|
||||
// between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is
|
||||
// incremented with each injection. References to the IDs are adjusted accordingly.
|
||||
// We assume tha all IDs within the injected SVG are unique, therefore the same suffix can be used for all IDs of one
|
||||
// injected SVG.
|
||||
// If the onlyReferenced argument is set to true, only those IDs will be made unique that are referenced from within the SVG
|
||||
function makeIdsUnique(svgElem, onlyReferenced) {
|
||||
var idSuffix = ID_SUFFIX + uniqueIdCounter++;
|
||||
// Regular expression for functional notations of an IRI references. This will find occurences in the form
|
||||
// url(#anyId) or url("#anyId") (for Internet Explorer) and capture the referenced ID
|
||||
var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g;
|
||||
// Get all elements with an ID. The SVG spec recommends to put referenced elements inside <defs> elements, but
|
||||
// this is not a requirement, therefore we have to search for IDs in the whole SVG.
|
||||
var idElements = svgElem.querySelectorAll('[id]');
|
||||
var idElem;
|
||||
// An object containing referenced IDs as keys is used if only referenced IDs should be uniquified.
|
||||
// If this object does not exist, all IDs will be uniquified.
|
||||
var referencedIds = onlyReferenced ? [] : NULL;
|
||||
var tagName;
|
||||
var iriTagNames = {};
|
||||
var iriProperties = [];
|
||||
var changed = false;
|
||||
var i, j;
|
||||
|
||||
if (idElements[_LENGTH_]) {
|
||||
// Make all IDs unique by adding the ID suffix and collect all encountered tag names
|
||||
// that are IRI referenceable from properities.
|
||||
for (i = 0; i < idElements[_LENGTH_]; i++) {
|
||||
tagName = idElements[i].localName; // Use non-namespaced tag name
|
||||
// Make ID unique if tag name is IRI referenceable
|
||||
if (tagName in IRI_TAG_PROPERTIES_MAP) {
|
||||
iriTagNames[tagName] = 1;
|
||||
}
|
||||
}
|
||||
// Get all properties that are mapped to the found IRI referenceable tags
|
||||
for (tagName in iriTagNames) {
|
||||
(IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) {
|
||||
// Add mapped properties to array of iri referencing properties.
|
||||
// Use linear search here because the number of possible entries is very small (maximum 11)
|
||||
if (iriProperties.indexOf(mappedProperty) < 0) {
|
||||
iriProperties.push(mappedProperty);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (iriProperties[_LENGTH_]) {
|
||||
// Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"'
|
||||
iriProperties.push(_STYLE_);
|
||||
}
|
||||
// Run through all elements of the SVG and replace IDs in references.
|
||||
// To get all descending elements, getElementsByTagName('*') seems to perform faster than querySelectorAll('*').
|
||||
// Since svgElem.getElementsByTagName('*') does not return the svg element itself, we have to handle it separately.
|
||||
var descElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*');
|
||||
var element = svgElem;
|
||||
var propertyName;
|
||||
var value;
|
||||
var newValue;
|
||||
for (i = -1; element != NULL;) {
|
||||
if (element.localName == _STYLE_) {
|
||||
// If element is a style element, replace IDs in all occurences of "url(#anyId)" in text content
|
||||
value = element.textContent;
|
||||
newValue = value && value.replace(funcIriRegex, function(match, id) {
|
||||
if (referencedIds) {
|
||||
referencedIds[id] = 1;
|
||||
}
|
||||
return 'url(#' + id + idSuffix + ')';
|
||||
});
|
||||
if (newValue !== value) {
|
||||
element.textContent = newValue;
|
||||
}
|
||||
} else if (element.hasAttributes()) {
|
||||
// Run through all property names for which IDs were found
|
||||
for (j = 0; j < iriProperties[_LENGTH_]; j++) {
|
||||
propertyName = iriProperties[j];
|
||||
value = element[_GET_ATTRIBUTE_](propertyName);
|
||||
newValue = value && value.replace(funcIriRegex, function(match, id) {
|
||||
if (referencedIds) {
|
||||
referencedIds[id] = 1;
|
||||
}
|
||||
return 'url(#' + id + idSuffix + ')';
|
||||
});
|
||||
if (newValue !== value) {
|
||||
element[_SET_ATTRIBUTE_](propertyName, newValue);
|
||||
}
|
||||
}
|
||||
// Replace IDs in xlink:ref and href attributes
|
||||
['xlink:href', 'href'].forEach(function(refAttrName) {
|
||||
var iri = element[_GET_ATTRIBUTE_](refAttrName);
|
||||
if (/^\s*#/.test(iri)) { // Check if iri is non-null and internal reference
|
||||
iri = iri.trim();
|
||||
element[_SET_ATTRIBUTE_](refAttrName, iri + idSuffix);
|
||||
if (referencedIds) {
|
||||
// Add ID to referenced IDs
|
||||
referencedIds[iri.substring(1)] = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
element = descElements[++i];
|
||||
}
|
||||
for (i = 0; i < idElements[_LENGTH_]; i++) {
|
||||
idElem = idElements[i];
|
||||
// If set of referenced IDs exists, make only referenced IDs unique,
|
||||
// otherwise make all IDs unique.
|
||||
if (!referencedIds || referencedIds[idElem.id]) {
|
||||
// Add suffix to element's ID
|
||||
idElem.id += idSuffix;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// return true if SVG element has changed
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
// For cached SVGs the IDs are made unique by simply replacing the already inserted unique IDs with a
|
||||
// higher ID counter. This is much more performant than a call to makeIdsUnique().
|
||||
function makeIdsUniqueCached(svgString) {
|
||||
return svgString.replace(ID_SUFFIX_REGEX, ID_SUFFIX + uniqueIdCounter++);
|
||||
}
|
||||
|
||||
|
||||
// Inject SVG by replacing the img element with the SVG element in the DOM
|
||||
function inject(imgElem, svgElem, absUrl, options) {
|
||||
if (svgElem) {
|
||||
svgElem[_SET_ATTRIBUTE_]('data-inject-url', absUrl);
|
||||
var parentNode = imgElem.parentNode;
|
||||
if (parentNode) {
|
||||
if (options.copyAttributes) {
|
||||
copyAttributes(imgElem, svgElem);
|
||||
}
|
||||
// Invoke beforeInject hook if set
|
||||
var beforeInject = options.beforeInject;
|
||||
var injectElem = (beforeInject && beforeInject(imgElem, svgElem)) || svgElem;
|
||||
// Replace img element with new element. This is the actual injection.
|
||||
parentNode.replaceChild(injectElem, imgElem);
|
||||
// Mark img element as injected
|
||||
imgElem[__SVGINJECT] = INJECTED;
|
||||
removeOnLoadAttribute(imgElem);
|
||||
// Invoke afterInject hook if set
|
||||
var afterInject = options.afterInject;
|
||||
if (afterInject) {
|
||||
afterInject(imgElem, injectElem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
svgInvalid(imgElem, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Merges any number of options objects into a new object
|
||||
function mergeOptions() {
|
||||
var mergedOptions = {};
|
||||
var args = arguments;
|
||||
// Iterate over all specified options objects and add all properties to the new options object
|
||||
for (var i = 0; i < args[_LENGTH_]; i++) {
|
||||
var argument = args[i];
|
||||
for (var key in argument) {
|
||||
if (argument.hasOwnProperty(key)) {
|
||||
mergedOptions[key] = argument[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return mergedOptions;
|
||||
}
|
||||
|
||||
|
||||
// Adds the specified CSS to the document's <head> element
|
||||
function addStyleToHead(css) {
|
||||
var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0];
|
||||
if (head) {
|
||||
var style = document[_CREATE_ELEMENT_](_STYLE_);
|
||||
style.type = 'text/css';
|
||||
style.appendChild(document.createTextNode(css));
|
||||
head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Builds an SVG element from the specified SVG string
|
||||
function buildSvgElement(svgStr, verify) {
|
||||
if (verify) {
|
||||
var svgDoc;
|
||||
try {
|
||||
// Parse the SVG string with DOMParser
|
||||
svgDoc = svgStringToSvgDoc(svgStr);
|
||||
} catch(e) {
|
||||
return NULL;
|
||||
}
|
||||
if (svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) {
|
||||
// DOMParser does not throw an exception, but instead puts parsererror tags in the document
|
||||
return NULL;
|
||||
}
|
||||
return svgDoc.documentElement;
|
||||
} else {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = svgStr;
|
||||
return div.firstElementChild;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function removeOnLoadAttribute(imgElem) {
|
||||
// Remove the onload attribute. Should only be used to remove the unstyled image flash protection and
|
||||
// make the element visible, not for removing the event listener.
|
||||
imgElem.removeAttribute('onload');
|
||||
}
|
||||
|
||||
|
||||
function errorMessage(msg) {
|
||||
console.error('SVGInject: ' + msg);
|
||||
}
|
||||
|
||||
|
||||
function fail(imgElem, status, options) {
|
||||
imgElem[__SVGINJECT] = FAIL;
|
||||
if (options.onFail) {
|
||||
options.onFail(imgElem, status);
|
||||
} else {
|
||||
errorMessage(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function svgInvalid(imgElem, options) {
|
||||
removeOnLoadAttribute(imgElem);
|
||||
fail(imgElem, SVG_INVALID, options);
|
||||
}
|
||||
|
||||
|
||||
function svgNotSupported(imgElem, options) {
|
||||
removeOnLoadAttribute(imgElem);
|
||||
fail(imgElem, SVG_NOT_SUPPORTED, options);
|
||||
}
|
||||
|
||||
|
||||
function loadFail(imgElem, options) {
|
||||
fail(imgElem, LOAD_FAIL, options);
|
||||
}
|
||||
|
||||
|
||||
function removeEventListeners(imgElem) {
|
||||
imgElem.onload = NULL;
|
||||
imgElem.onerror = NULL;
|
||||
}
|
||||
|
||||
|
||||
function imgNotSet(msg) {
|
||||
errorMessage('no img element');
|
||||
}
|
||||
|
||||
|
||||
function createSVGInject(globalName, options) {
|
||||
var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options);
|
||||
var svgLoadCache = {};
|
||||
|
||||
if (IS_SVG_SUPPORTED) {
|
||||
// If the browser supports SVG, add a small stylesheet that hides the <img> elements until
|
||||
// injection is finished. This avoids showing the unstyled SVGs before style is applied.
|
||||
addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SVGInject
|
||||
*
|
||||
* Injects the SVG specified in the `src` attribute of the specified `img` element or array of `img`
|
||||
* elements. Returns a Promise object which resolves if all passed in `img` elements have either been
|
||||
* injected or failed to inject (Only if a global Promise object is available like in all modern browsers
|
||||
* or through a polyfill).
|
||||
*
|
||||
* Options:
|
||||
* useCache: If set to `true` the SVG will be cached using the absolute URL. Default value is `true`.
|
||||
* copyAttributes: If set to `true` the attributes will be copied from `img` to `svg`. Dfault value
|
||||
* is `true`.
|
||||
* makeIdsUnique: If set to `true` the ID of elements in the `<defs>` element that can be references by
|
||||
* property values (for example 'clipPath') are made unique by appending "--inject-X", where X is a
|
||||
* running number which increases with each injection. This is done to avoid duplicate IDs in the DOM.
|
||||
* beforeLoad: Hook before SVG is loaded. The `img` element is passed as a parameter. If the hook returns
|
||||
* a string it is used as the URL instead of the `img` element's `src` attribute.
|
||||
* afterLoad: Hook after SVG is loaded. The loaded `svg` element and `svg` string are passed as a
|
||||
* parameters. If caching is active this hook will only get called once for injected SVGs with the
|
||||
* same absolute path. Changes to the `svg` element in this hook will be applied to all injected SVGs
|
||||
* with the same absolute path. It's also possible to return an `svg` string or `svg` element which
|
||||
* will then be used for the injection.
|
||||
* beforeInject: Hook before SVG is injected. The `img` and `svg` elements are passed as parameters. If
|
||||
* any html element is returned it gets injected instead of applying the default SVG injection.
|
||||
* afterInject: Hook after SVG is injected. The `img` and `svg` elements are passed as parameters.
|
||||
* onAllFinish: Hook after all `img` elements passed to an SVGInject() call have either been injected or
|
||||
* failed to inject.
|
||||
* onFail: Hook after injection fails. The `img` element and a `status` string are passed as an parameter.
|
||||
* The `status` can be either `'SVG_NOT_SUPPORTED'` (the browser does not support SVG),
|
||||
* `'SVG_INVALID'` (the SVG is not in a valid format) or `'LOAD_FAILED'` (loading of the SVG failed).
|
||||
*
|
||||
* @param {HTMLImageElement} img - an img element or an array of img elements
|
||||
* @param {Object} [options] - optional parameter with [options](#options) for this injection.
|
||||
*/
|
||||
function SVGInject(img, options) {
|
||||
options = mergeOptions(defaultOptions, options);
|
||||
|
||||
var run = function(resolve) {
|
||||
var allFinish = function() {
|
||||
var onAllFinish = options.onAllFinish;
|
||||
if (onAllFinish) {
|
||||
onAllFinish();
|
||||
}
|
||||
resolve && resolve();
|
||||
};
|
||||
|
||||
if (img && typeof img[_LENGTH_] != _UNDEFINED_) {
|
||||
// an array like structure of img elements
|
||||
var injectIndex = 0;
|
||||
var injectCount = img[_LENGTH_];
|
||||
|
||||
if (injectCount == 0) {
|
||||
allFinish();
|
||||
} else {
|
||||
var finish = function() {
|
||||
if (++injectIndex == injectCount) {
|
||||
allFinish();
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < injectCount; i++) {
|
||||
SVGInjectElement(img[i], options, finish);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// only one img element
|
||||
SVGInjectElement(img, options, allFinish);
|
||||
}
|
||||
};
|
||||
|
||||
// return a Promise object if globally available
|
||||
return typeof Promise == _UNDEFINED_ ? run() : new Promise(run);
|
||||
}
|
||||
|
||||
|
||||
// Injects a single svg element. Options must be already merged with the default options.
|
||||
function SVGInjectElement(imgElem, options, callback) {
|
||||
if (imgElem) {
|
||||
var svgInjectAttributeValue = imgElem[__SVGINJECT];
|
||||
if (!svgInjectAttributeValue) {
|
||||
removeEventListeners(imgElem);
|
||||
|
||||
if (!IS_SVG_SUPPORTED) {
|
||||
svgNotSupported(imgElem, options);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
// Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load
|
||||
// URL path. Else use the imgElem's src attribute value.
|
||||
var beforeLoad = options.beforeLoad;
|
||||
var src = (beforeLoad && beforeLoad(imgElem)) || imgElem[_GET_ATTRIBUTE_]('src');
|
||||
|
||||
if (!src) {
|
||||
// If no image src attribute is set do no injection. This can only be reached by using javascript
|
||||
// because if no src attribute is set the onload and onerror events do not get called
|
||||
if (src === '') {
|
||||
loadFail(imgElem, options);
|
||||
}
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// set array so later calls can register callbacks
|
||||
var onFinishCallbacks = [];
|
||||
imgElem[__SVGINJECT] = onFinishCallbacks;
|
||||
|
||||
var onFinish = function() {
|
||||
callback();
|
||||
onFinishCallbacks.forEach(function(onFinishCallback) {
|
||||
onFinishCallback();
|
||||
});
|
||||
};
|
||||
|
||||
var absUrl = getAbsoluteUrl(src);
|
||||
var useCacheOption = options.useCache;
|
||||
var makeIdsUniqueOption = options.makeIdsUnique;
|
||||
|
||||
var setSvgLoadCacheValue = function(val) {
|
||||
if (useCacheOption) {
|
||||
svgLoadCache[absUrl].forEach(function(svgLoad) {
|
||||
svgLoad(val);
|
||||
});
|
||||
svgLoadCache[absUrl] = val;
|
||||
}
|
||||
};
|
||||
|
||||
if (useCacheOption) {
|
||||
var svgLoad = svgLoadCache[absUrl];
|
||||
|
||||
var handleLoadValue = function(loadValue) {
|
||||
if (loadValue === LOAD_FAIL) {
|
||||
loadFail(imgElem, options);
|
||||
} else if (loadValue === SVG_INVALID) {
|
||||
svgInvalid(imgElem, options);
|
||||
} else {
|
||||
var hasUniqueIds = loadValue[0];
|
||||
var svgString = loadValue[1];
|
||||
var uniqueIdsSvgString = loadValue[2];
|
||||
var svgElem;
|
||||
|
||||
if (makeIdsUniqueOption) {
|
||||
if (hasUniqueIds === NULL) {
|
||||
// IDs for the SVG string have not been made unique before. This may happen if previous
|
||||
// injection of a cached SVG have been run with the option makedIdsUnique set to false
|
||||
svgElem = buildSvgElement(svgString, false);
|
||||
hasUniqueIds = makeIdsUnique(svgElem, false);
|
||||
|
||||
loadValue[0] = hasUniqueIds;
|
||||
loadValue[2] = hasUniqueIds && svgElemToSvgString(svgElem);
|
||||
} else if (hasUniqueIds) {
|
||||
// Make IDs unique for already cached SVGs with better performance
|
||||
svgString = makeIdsUniqueCached(uniqueIdsSvgString);
|
||||
}
|
||||
}
|
||||
|
||||
svgElem = svgElem || buildSvgElement(svgString, false);
|
||||
|
||||
inject(imgElem, svgElem, absUrl, options);
|
||||
}
|
||||
onFinish();
|
||||
};
|
||||
|
||||
if (typeof svgLoad != _UNDEFINED_) {
|
||||
// Value for url exists in cache
|
||||
if (svgLoad.isCallbackQueue) {
|
||||
// Same url has been cached, but value has not been loaded yet, so add to callbacks
|
||||
svgLoad.push(handleLoadValue);
|
||||
} else {
|
||||
handleLoadValue(svgLoad);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
var svgLoad = [];
|
||||
// set property isCallbackQueue to Array to differentiate from array with cached loaded values
|
||||
svgLoad.isCallbackQueue = true;
|
||||
svgLoadCache[absUrl] = svgLoad;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the SVG because it is not cached or caching is disabled
|
||||
loadSvg(absUrl, function(svgXml, svgString) {
|
||||
// Use the XML from the XHR request if it is an instance of Document. Otherwise
|
||||
// (for example of IE9), create the svg document from the svg string.
|
||||
var svgElem = svgXml instanceof Document ? svgXml.documentElement : buildSvgElement(svgString, true);
|
||||
|
||||
var afterLoad = options.afterLoad;
|
||||
if (afterLoad) {
|
||||
// Invoke afterLoad hook which may modify the SVG element. After load may also return a new
|
||||
// svg element or svg string
|
||||
var svgElemOrSvgString = afterLoad(svgElem, svgString) || svgElem;
|
||||
if (svgElemOrSvgString) {
|
||||
// Update svgElem and svgString because of modifications to the SVG element or SVG string in
|
||||
// the afterLoad hook, so the modified SVG is also used for all later cached injections
|
||||
var isString = typeof svgElemOrSvgString == 'string';
|
||||
svgString = isString ? svgElemOrSvgString : svgElemToSvgString(svgElem);
|
||||
svgElem = isString ? buildSvgElement(svgElemOrSvgString, true) : svgElemOrSvgString;
|
||||
}
|
||||
}
|
||||
|
||||
if (svgElem instanceof SVGElement) {
|
||||
var hasUniqueIds = NULL;
|
||||
if (makeIdsUniqueOption) {
|
||||
hasUniqueIds = makeIdsUnique(svgElem, false);
|
||||
}
|
||||
|
||||
if (useCacheOption) {
|
||||
var uniqueIdsSvgString = hasUniqueIds && svgElemToSvgString(svgElem);
|
||||
// set an array with three entries to the load cache
|
||||
setSvgLoadCacheValue([hasUniqueIds, svgString, uniqueIdsSvgString]);
|
||||
}
|
||||
|
||||
inject(imgElem, svgElem, absUrl, options);
|
||||
} else {
|
||||
svgInvalid(imgElem, options);
|
||||
setSvgLoadCacheValue(SVG_INVALID);
|
||||
}
|
||||
onFinish();
|
||||
}, function() {
|
||||
loadFail(imgElem, options);
|
||||
setSvgLoadCacheValue(LOAD_FAIL);
|
||||
onFinish();
|
||||
});
|
||||
} else {
|
||||
if (Array.isArray(svgInjectAttributeValue)) {
|
||||
// svgInjectAttributeValue is an array. Injection is not complete so register callback
|
||||
svgInjectAttributeValue.push(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgNotSet();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the default [options](#options) for SVGInject.
|
||||
*
|
||||
* @param {Object} [options] - default [options](#options) for an injection.
|
||||
*/
|
||||
SVGInject.setOptions = function(options) {
|
||||
defaultOptions = mergeOptions(defaultOptions, options);
|
||||
};
|
||||
|
||||
|
||||
// Create a new instance of SVGInject
|
||||
SVGInject.create = createSVGInject;
|
||||
|
||||
|
||||
/**
|
||||
* Used in onerror Event of an `<img>` element to handle cases when the loading the original src fails
|
||||
* (for example if file is not found or if the browser does not support SVG). This triggers a call to the
|
||||
* options onFail hook if available. The optional second parameter will be set as the new src attribute
|
||||
* for the img element.
|
||||
*
|
||||
* @param {HTMLImageElement} img - an img element
|
||||
* @param {String} [fallbackSrc] - optional parameter fallback src
|
||||
*/
|
||||
SVGInject.err = function(img, fallbackSrc) {
|
||||
if (img) {
|
||||
if (img[__SVGINJECT] != FAIL) {
|
||||
removeEventListeners(img);
|
||||
|
||||
if (!IS_SVG_SUPPORTED) {
|
||||
svgNotSupported(img, defaultOptions);
|
||||
} else {
|
||||
removeOnLoadAttribute(img);
|
||||
loadFail(img, defaultOptions);
|
||||
}
|
||||
if (fallbackSrc) {
|
||||
removeOnLoadAttribute(img);
|
||||
img.src = fallbackSrc;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgNotSet();
|
||||
}
|
||||
};
|
||||
|
||||
window[globalName] = SVGInject;
|
||||
|
||||
return SVGInject;
|
||||
}
|
||||
|
||||
var SVGInjectInstance = createSVGInject('SVGInject');
|
||||
|
||||
if (typeof module == 'object' && typeof module.exports == 'object') {
|
||||
module.exports = SVGInjectInstance;
|
||||
}
|
||||
})(window, document);
|
410
public/script.js
410
public/script.js
@@ -1,7 +1,7 @@
|
||||
import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, } from "./scripts/RossAscends-mods.js";
|
||||
import { userStatsHandler, statMesProcess } from './scripts/stats.js';
|
||||
import { encode } from "../scripts/gpt-2-3-tokenizer/mod.js";
|
||||
import { GPT3BrowserTokenizer } from "../scripts/gpt-3-tokenizer/gpt3-tokenizer.js";
|
||||
import { encode } from "../lib/gpt-2-3-tokenizer/mod.js";
|
||||
import { GPT3BrowserTokenizer } from "../lib/gpt-3-tokenizer/gpt3-tokenizer.js";
|
||||
import {
|
||||
generateKoboldWithStreaming,
|
||||
kai_settings,
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
renameGroupChat,
|
||||
importGroupChat,
|
||||
getGroupBlock,
|
||||
getGroupChatNames,
|
||||
} from "./scripts/group-chats.js";
|
||||
|
||||
import {
|
||||
@@ -65,9 +66,6 @@ import {
|
||||
power_user,
|
||||
pygmalion_options,
|
||||
tokenizers,
|
||||
formatInstructModeChat,
|
||||
formatInstructStoryString,
|
||||
formatInstructModePrompt,
|
||||
persona_description_positions,
|
||||
loadMovingUIState,
|
||||
getCustomStoppingStrings,
|
||||
@@ -157,7 +155,7 @@ import {
|
||||
secret_state,
|
||||
writeSecret
|
||||
} from "./scripts/secrets.js";
|
||||
import { EventEmitter } from './scripts/eventemitter.js';
|
||||
import { EventEmitter } from './lib/eventemitter.js';
|
||||
import { markdownExclusionExt } from "./scripts/showdown-exclusion.js";
|
||||
import { NOTE_MODULE_NAME, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from "./scripts/authors-note.js";
|
||||
import { deviceInfo } from "./scripts/RossAscends-mods.js";
|
||||
@@ -165,6 +163,13 @@ import { registerPromptManagerMigration } from "./scripts/PromptManager.js";
|
||||
import { getRegexedString, regex_placement } from "./scripts/extensions/regex/engine.js";
|
||||
import { FILTER_TYPES, FilterHelper } from "./scripts/filters.js";
|
||||
import { getCfgPrompt, getGuidanceScale } from "./scripts/extensions/cfg/util.js";
|
||||
import {
|
||||
formatInstructModeChat,
|
||||
formatInstructModePrompt,
|
||||
formatInstructModeExamples,
|
||||
getInstructStoppingSequences,
|
||||
autoSelectInstructPreset,
|
||||
} from "./scripts/instruct-mode.js";
|
||||
|
||||
//exporting functions and vars for mods
|
||||
export {
|
||||
@@ -330,7 +335,6 @@ let scrollLock = false;
|
||||
const durationSaveEdit = 1000;
|
||||
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
|
||||
export const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit);
|
||||
const getStatusDebounced = debounce(() => getStatus(), 300_000);
|
||||
const saveChatDebounced = debounce(() => saveChatConditional(), durationSaveEdit);
|
||||
|
||||
const system_message_types = {
|
||||
@@ -752,7 +756,7 @@ let create_save = {
|
||||
};
|
||||
|
||||
//animation right menu
|
||||
let animation_duration = 250;
|
||||
let animation_duration = 125;
|
||||
let animation_easing = "ease-in-out";
|
||||
let popup_type = "";
|
||||
let bg_file_for_del = "";
|
||||
@@ -884,10 +888,6 @@ async function getStatus() {
|
||||
const hordeStatus = await checkHordeStatus();
|
||||
online_status = hordeStatus ? 'Connected' : 'no_connection';
|
||||
resultCheckStatus();
|
||||
|
||||
if (online_status !== "no_connection") {
|
||||
getStatusDebounced();
|
||||
}
|
||||
}
|
||||
catch {
|
||||
online_status = "no_connection";
|
||||
@@ -916,6 +916,10 @@ async function getStatus() {
|
||||
if (online_status == undefined) {
|
||||
online_status = "no_connection";
|
||||
}
|
||||
|
||||
// Determine instruct mode preset
|
||||
autoSelectInstructPreset(online_status);
|
||||
|
||||
if ((online_status.toLowerCase().indexOf("pygmalion") != -1 && power_user.pygmalion_formatting == pygmalion_options.AUTO)
|
||||
|| (online_status !== "no_connection" && power_user.pygmalion_formatting == pygmalion_options.ENABLED)) {
|
||||
is_pygmalion = true;
|
||||
@@ -937,9 +941,6 @@ async function getStatus() {
|
||||
|
||||
//console.log(online_status);
|
||||
resultCheckStatus();
|
||||
if (online_status !== "no_connection") {
|
||||
getStatusDebounced();
|
||||
}
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
console.log(exception);
|
||||
@@ -1042,7 +1043,7 @@ async function printCharacters(fullRefresh = false) {
|
||||
$("#rm_print_characters_pagination").pagination({
|
||||
dataSource: getEntitiesList({ doFilter: true }),
|
||||
pageSize: Number(localStorage.getItem(storageKey)) || 50,
|
||||
sizeChangerOptions: [25, 50, 100, 250, 500, 1000],
|
||||
sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000],
|
||||
pageRange: 1,
|
||||
position: 'top',
|
||||
showPageNumbers: false,
|
||||
@@ -1090,6 +1091,30 @@ export function getEntitiesList({ doFilter } = {}) {
|
||||
return entities;
|
||||
}
|
||||
|
||||
async function getOneCharacter(avatarUrl) {
|
||||
const response = await fetch("/getonecharacter", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
avatar_url: avatarUrl,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const getData = await response.json();
|
||||
getData['name'] = DOMPurify.sanitize(getData['name']);
|
||||
getData['chat'] = String(getData['chat']);
|
||||
|
||||
const indexOf = characters.findIndex(x => x.avatar === avatarUrl);
|
||||
|
||||
if (indexOf !== -1) {
|
||||
characters[indexOf] = getData;
|
||||
} else {
|
||||
toastr.error(`Character ${avatarUrl} not found in the list`, "Error", { timeOut: 5000, preventDuplicates: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getCharacters() {
|
||||
var response = await fetch("/getcharacters", {
|
||||
method: "POST",
|
||||
@@ -1423,25 +1448,25 @@ function insertSVGIcon(mes, extra) {
|
||||
modelName = extra.api;
|
||||
}
|
||||
|
||||
// Fetch the SVG based on the modelName
|
||||
$.get(`/img/${modelName}.svg`, function (data) {
|
||||
// Extract the SVG content from the XML data
|
||||
let svg = $(data).find('svg');
|
||||
|
||||
// Add classes for styling and identification
|
||||
svg.addClass('icon-svg timestamp-icon');
|
||||
const image = new Image();
|
||||
// Add classes for styling and identification
|
||||
image.classList.add('icon-svg', 'timestamp-icon');
|
||||
image.src = `/img/${modelName}.svg`;
|
||||
|
||||
image.onload = async function () {
|
||||
// Check if an SVG already exists adjacent to the timestamp
|
||||
let existingSVG = mes.find('.timestamp').next('.timestamp-icon');
|
||||
|
||||
if (existingSVG.length) {
|
||||
// Replace existing SVG
|
||||
existingSVG.replaceWith(svg);
|
||||
existingSVG.replaceWith(image);
|
||||
} else {
|
||||
// Append the new SVG if none exists
|
||||
mes.find('.timestamp').after(svg);
|
||||
mes.find('.timestamp').after(image);
|
||||
}
|
||||
});
|
||||
|
||||
await SVGInject(this);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1581,8 +1606,18 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true
|
||||
mes.is_user,
|
||||
);
|
||||
const bias = messageFormatting(mes.extra?.bias ?? "");
|
||||
const bookmarkLink = mes?.extra?.bookmark_link ?? '';
|
||||
let bookmarkLink = mes?.extra?.bookmark_link ?? '';
|
||||
// Verify bookmarked chat still exists
|
||||
// Cohee: Commented out for now. I'm worried of performance issues.
|
||||
/*if (bookmarkLink !== '') {
|
||||
let chat_names = selected_group
|
||||
? getGroupChatNames(selected_group)
|
||||
: Object.values(getPastCharacterChats()).map(({ file_name }) => file_name);
|
||||
|
||||
if (!chat_names.includes(bookmarkLink)) {
|
||||
bookmarkLink = ''
|
||||
}
|
||||
}*/
|
||||
let params = {
|
||||
mesId: count_view_mes,
|
||||
characterName: characterName,
|
||||
@@ -1746,7 +1781,6 @@ function scrollChatToBottom() {
|
||||
function substituteParams(content, _name1, _name2, _original, _group) {
|
||||
_name1 = _name1 ?? name1;
|
||||
_name2 = _name2 ?? name2;
|
||||
_original = _original || '';
|
||||
_group = _group ?? name2;
|
||||
|
||||
if (!content) {
|
||||
@@ -1880,27 +1914,7 @@ function getStoppingStrings(isImpersonate, addSpace) {
|
||||
}
|
||||
}
|
||||
|
||||
function addInstructSequence(sequence) {
|
||||
// Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string
|
||||
// But it's a problem for Metharme which doesn't use newlines to separate them.
|
||||
const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s;
|
||||
// Sequence must be a non-empty string
|
||||
if (typeof sequence === 'string' && sequence.length > 0) {
|
||||
// If sequence is just a whitespace or newline - we don't want to make it a stopping string
|
||||
// User can always add it as a custom stop string if really needed
|
||||
if (sequence.trim().length > 0) {
|
||||
const wrappedSequence = wrap(sequence);
|
||||
// Need to respect "insert macro" setting
|
||||
const stopString = power_user.instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence;
|
||||
result.push(stopString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (power_user.instruct.enabled) {
|
||||
addInstructSequence(power_user.instruct.input_sequence);
|
||||
addInstructSequence(power_user.instruct.output_sequence);
|
||||
}
|
||||
result.push(...getInstructStoppingSequences());
|
||||
|
||||
if (power_user.custom_stopping_strings) {
|
||||
const customStoppingStrings = getCustomStoppingStrings();
|
||||
@@ -2042,9 +2056,8 @@ function getPersonaDescription(storyString) {
|
||||
|
||||
switch (power_user.persona_description_position) {
|
||||
case persona_description_positions.BEFORE_CHAR:
|
||||
return `${substituteParams(power_user.persona_description)}\n${storyString}`;
|
||||
case persona_description_positions.AFTER_CHAR:
|
||||
return `${storyString}${substituteParams(power_user.persona_description)}\n`;
|
||||
return storyString;
|
||||
default:
|
||||
if (shouldWIAddPrompt) {
|
||||
const originalAN = extension_prompts[NOTE_MODULE_NAME].value
|
||||
@@ -2098,12 +2111,14 @@ function baseChatReplace(value, name1, name2) {
|
||||
if (power_user.collapse_newlines) {
|
||||
value = collapseNewlines(value);
|
||||
}
|
||||
|
||||
value = value.replace(/\r/g, '');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function isStreamingEnabled() {
|
||||
return ((main_api == 'openai' && oai_settings.stream_openai && oai_settings.chat_completion_source !== chat_completion_sources.SCALE)
|
||||
return ((main_api == 'openai' && oai_settings.stream_openai && oai_settings.chat_completion_source !== chat_completion_sources.SCALE && oai_settings.chat_completion_source !== chat_completion_sources.AI21)
|
||||
|| (main_api == 'kobold' && kai_settings.streaming_kobold && kai_settings.can_use_streaming)
|
||||
|| (main_api == 'novel' && nai_settings.streaming_novel)
|
||||
|| (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming))
|
||||
@@ -2349,10 +2364,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
abortController = new AbortController();
|
||||
}
|
||||
|
||||
if (main_api == 'novel' && quiet_prompt) {
|
||||
quiet_prompt = adjustNovelInstructionPrompt(quiet_prompt);
|
||||
}
|
||||
|
||||
// OpenAI doesn't need instruct mode. Use OAI main prompt instead.
|
||||
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
|
||||
const isImpersonate = type == "impersonate";
|
||||
@@ -2441,6 +2452,11 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
}
|
||||
}
|
||||
|
||||
if (quiet_prompt) {
|
||||
quiet_prompt = substituteParams(quiet_prompt);
|
||||
quiet_prompt = main_api == 'novel' ? adjustNovelInstructionPrompt(quiet_prompt) : quiet_prompt;
|
||||
}
|
||||
|
||||
if (true === dryRun ||
|
||||
(online_status != 'no_connection' && this_chid != undefined && this_chid !== 'invalid-safety-id')) {
|
||||
let textareaText;
|
||||
@@ -2498,10 +2514,15 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
const scenarioText = chat_metadata['scenario'] || characters[this_chid].scenario;
|
||||
let charDescription = baseChatReplace(characters[this_chid].description.trim(), name1, name2);
|
||||
let charPersonality = baseChatReplace(characters[this_chid].personality.trim(), name1, name2);
|
||||
let personaDescription = baseChatReplace(power_user.persona_description.trim(), name1, name2);
|
||||
let Scenario = baseChatReplace(scenarioText.trim(), name1, name2);
|
||||
let mesExamples = baseChatReplace(characters[this_chid].mes_example.trim(), name1, name2);
|
||||
let systemPrompt = baseChatReplace(characters[this_chid].data?.system_prompt?.trim(), name1, name2);
|
||||
let jailbreakPrompt = baseChatReplace(characters[this_chid].data?.post_history_instructions?.trim(), name1, name2);
|
||||
let systemPrompt = power_user.prefer_character_prompt ? baseChatReplace(characters[this_chid].data?.system_prompt?.trim(), name1, name2) : '';
|
||||
let jailbreakPrompt = power_user.prefer_character_jailbreak ? baseChatReplace(characters[this_chid].data?.post_history_instructions?.trim(), name1, name2) : '';
|
||||
|
||||
if (isInstruct) {
|
||||
systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : baseChatReplace(power_user.instruct.system_prompt, name1, name2);
|
||||
}
|
||||
|
||||
// Parse example messages
|
||||
if (!mesExamples.startsWith('<START>')) {
|
||||
@@ -2510,11 +2531,17 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
if (mesExamples.replace(/<START>/gi, '').trim().length === 0) {
|
||||
mesExamples = '';
|
||||
}
|
||||
if (mesExamples && isInstruct) {
|
||||
mesExamples = formatInstructModeExamples(mesExamples, name1, name2)
|
||||
}
|
||||
|
||||
const exampleSeparator = power_user.context.example_separator ? `${power_user.context.example_separator}\n` : '';
|
||||
const blockHeading = main_api === 'openai' ? '<START>\n' : exampleSeparator;
|
||||
let mesExamplesArray = mesExamples.split(/<START>/gi).slice(1).map(block => `${blockHeading}${block.trim()}\n`);
|
||||
|
||||
if (power_user.strip_examples)
|
||||
mesExamplesArray = []
|
||||
|
||||
// First message in fresh 1-on-1 chat reacts to user/character settings changes
|
||||
if (chat.length) {
|
||||
chat[0].mes = substituteParams(chat[0].mes);
|
||||
@@ -2541,7 +2568,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
const storyStringParams = {
|
||||
description: charDescription,
|
||||
personality: charPersonality,
|
||||
persona: personaDescription,
|
||||
scenario: Scenario,
|
||||
system: isInstruct ? systemPrompt : '',
|
||||
char: name2,
|
||||
user: name1,
|
||||
};
|
||||
@@ -2610,11 +2639,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
storyString = worldInfoBefore + storyString + worldInfoAfter;
|
||||
}
|
||||
|
||||
// Format the instruction string
|
||||
if (isInstruct) {
|
||||
storyString = formatInstructStoryString(storyString, systemPrompt);
|
||||
}
|
||||
|
||||
if (main_api === 'openai') {
|
||||
message_already_generated = ''; // OpenAI doesn't have multigen
|
||||
setOpenAIMessages(coreChat);
|
||||
@@ -3006,8 +3030,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
bias: promptBias,
|
||||
type: type,
|
||||
quietPrompt: quiet_prompt,
|
||||
jailbreakPrompt: jailbreakPrompt,
|
||||
cyclePrompt: cyclePrompt,
|
||||
systemPromptOverride: systemPrompt,
|
||||
jailbreakPromptOverride: jailbreakPrompt,
|
||||
}, dryRun);
|
||||
generate_data = { prompt: prompt };
|
||||
|
||||
@@ -4042,10 +4067,28 @@ function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete
|
||||
}
|
||||
}
|
||||
if (isInstruct && power_user.instruct.input_sequence && isImpersonate) {
|
||||
getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, '');
|
||||
//getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, '');
|
||||
power_user.instruct.input_sequence.split('\n')
|
||||
.filter(line => line.trim() !== '')
|
||||
.forEach(line => {
|
||||
getMessage = getMessage.replaceAll(line, '');
|
||||
});
|
||||
}
|
||||
if (isInstruct && power_user.instruct.output_sequence && !isImpersonate) {
|
||||
getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, '');
|
||||
//getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, '');
|
||||
power_user.instruct.output_sequence.split('\n')
|
||||
.filter(line => line.trim() !== '')
|
||||
.forEach(line => {
|
||||
getMessage = getMessage.replaceAll(line, '');
|
||||
});
|
||||
}
|
||||
if (isInstruct && power_user.instruct.last_output_sequence && !isImpersonate) {
|
||||
//getMessage = getMessage.replaceAll(power_user.instruct.last_output_sequence, '');
|
||||
power_user.instruct.last_output_sequence.split('\n')
|
||||
.filter(line => line.trim() !== '')
|
||||
.forEach(line => {
|
||||
getMessage = getMessage.replaceAll(line, '');
|
||||
});
|
||||
}
|
||||
// clean-up group message from excessive generations
|
||||
if (selected_group) {
|
||||
@@ -4571,10 +4614,9 @@ async function getChat() {
|
||||
chat_create_date = humanizedDateTime();
|
||||
}
|
||||
await getChatResult();
|
||||
await saveChat();
|
||||
saveChatDebounced();
|
||||
eventSource.emit('chatLoaded', { detail: { id: this_chid, character: characters[this_chid] } });
|
||||
|
||||
|
||||
setTimeout(function () {
|
||||
$('#send_textarea').click();
|
||||
$('#send_textarea').focus();
|
||||
@@ -4626,7 +4668,7 @@ async function openCharacterChat(file_name) {
|
||||
chat_metadata = {};
|
||||
await getChat();
|
||||
$("#selected_chat_pole").val(file_name);
|
||||
$("#create_button").click();
|
||||
await createOrEditCharacter();
|
||||
}
|
||||
|
||||
////////// OPTIMZED MAIN API CHANGE FUNCTION ////////////
|
||||
@@ -4745,6 +4787,7 @@ function changeMainAPI() {
|
||||
case chat_completion_sources.WINDOWAI:
|
||||
case chat_completion_sources.CLAUDE:
|
||||
case chat_completion_sources.OPENAI:
|
||||
case chat_completion_sources.AI21:
|
||||
default:
|
||||
setupChatCompletionPromptManager(oai_settings);
|
||||
break;
|
||||
@@ -5406,9 +5449,8 @@ async function getSettings(type) {
|
||||
setWorldInfoSettings(settings.world_info_settings ?? settings, data);
|
||||
|
||||
api_server_textgenerationwebui = settings.api_server_textgenerationwebui;
|
||||
$("#textgenerationwebui_api_url_text").val(
|
||||
api_server_textgenerationwebui
|
||||
);
|
||||
$("#textgenerationwebui_api_url_text").val(api_server_textgenerationwebui);
|
||||
$("#mancer_api_url_text").val(api_server_textgenerationwebui);
|
||||
api_use_mancer_webui = settings.api_use_mancer_webui
|
||||
$('#use-mancer-api-checkbox').prop("checked", api_use_mancer_webui);
|
||||
$('#use-mancer-api-checkbox').trigger("change");
|
||||
@@ -5630,6 +5672,66 @@ async function messageEditDone(div) {
|
||||
await saveChatConditional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the chat content for each chat file from the server and compiles them into a dictionary.
|
||||
* The function iterates over a provided list of chat metadata and requests the actual chat content
|
||||
* for each chat, either as an individual chat or a group chat based on the context.
|
||||
*
|
||||
* @param {Array} data - An array containing metadata about each chat such as file_name.
|
||||
* @param {boolean} isGroupChat - A flag indicating if the chat is a group chat.
|
||||
* @returns {Object} chat_dict - A dictionary where each key is a file_name and the value is the
|
||||
* corresponding chat content fetched from the server.
|
||||
*/
|
||||
export async function getChatsFromFiles(data, isGroupChat) {
|
||||
const context = getContext();
|
||||
let chat_dict = {};
|
||||
let chat_list = Object.values(data).sort((a, b) => a["file_name"].localeCompare(b["file_name"])).reverse();
|
||||
|
||||
for (const { file_name } of chat_list) {
|
||||
try {
|
||||
const endpoint = isGroupChat ? '/getgroupchat' : '/getchat';
|
||||
const requestBody = isGroupChat
|
||||
? JSON.stringify({ id: file_name })
|
||||
: JSON.stringify({
|
||||
ch_name: characters[context.characterId].name,
|
||||
file_name: file_name.replace('.jsonl', ''),
|
||||
avatar_url: characters[context.characterId].avatar
|
||||
});
|
||||
|
||||
const chatResponse = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: requestBody,
|
||||
cache: 'no-cache',
|
||||
});
|
||||
|
||||
if (!chatResponse.ok) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentChat = await chatResponse.json();
|
||||
if (!isGroupChat) {
|
||||
// remove the first message, which is metadata, only for individual chats
|
||||
currentChat.shift();
|
||||
}
|
||||
chat_dict[file_name] = currentChat;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return chat_dict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the metadata of all past chats related to a specific character based on its avatar URL.
|
||||
* The function sends a POST request to the server to retrieve all chats for the character. It then
|
||||
* processes the received data, sorts it by the file name, and returns the sorted data.
|
||||
*
|
||||
* @returns {Array} - An array containing metadata of all past chats of the character, sorted
|
||||
* in descending order by file name. Returns `undefined` if the fetch request is unsuccessful.
|
||||
*/
|
||||
async function getPastCharacterChats() {
|
||||
const response = await fetch("/getallchatsofcharacter", {
|
||||
method: 'POST',
|
||||
@@ -5647,6 +5749,12 @@ async function getPastCharacterChats() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the past chats for a character or a group based on the selected context.
|
||||
* The function first fetches the chats, processes them, and then displays them in
|
||||
* the HTML. It also has a built-in search functionality that allows filtering the
|
||||
* displayed chats based on a search query.
|
||||
*/
|
||||
export async function displayPastChats() {
|
||||
$("#select_chat_div").empty();
|
||||
|
||||
@@ -5655,45 +5763,69 @@ export async function displayPastChats() {
|
||||
const currentChat = selected_group ? group?.chat_id : characters[this_chid]["chat"];
|
||||
const displayName = selected_group ? group?.name : characters[this_chid].name;
|
||||
const avatarImg = selected_group ? group?.avatar_url : getThumbnailUrl('avatar', characters[this_chid]['avatar']);
|
||||
|
||||
const rawChats = await getChatsFromFiles(data, selected_group);
|
||||
// Sort by last message date descending
|
||||
data.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes)));
|
||||
|
||||
console.log(data);
|
||||
$("#load_select_chat_div").css("display", "none");
|
||||
$("#ChatHistoryCharName").text(displayName);
|
||||
for (const key in data) {
|
||||
let strlen = 300;
|
||||
let mes = data[key]["mes"];
|
||||
|
||||
if (mes !== undefined) {
|
||||
if (mes.length > strlen) {
|
||||
mes = "..." + mes.substring(mes.length - strlen);
|
||||
}
|
||||
const chat_items = data[key]["chat_items"];
|
||||
const file_size = data[key]["file_size"];
|
||||
const fileName = data[key]['file_name'];
|
||||
const timestamp = timestampToMoment(data[key]['last_mes']).format('LL LT');
|
||||
const template = $('#past_chat_template .select_chat_block_wrapper').clone();
|
||||
template.find('.select_chat_block').attr('file_name', fileName);
|
||||
template.find('.avatar img').attr('src', avatarImg);
|
||||
template.find('.select_chat_block_filename').text(fileName);
|
||||
template.find('.chat_file_size').text(" (" + file_size + ")");
|
||||
template.find('.chat_messages_num').text(" (" + chat_items + " messages)");
|
||||
template.find('.select_chat_block_mes').text(mes);
|
||||
template.find('.PastChat_cross').attr('file_name', fileName);
|
||||
template.find('.chat_messages_date').text(timestamp);
|
||||
const displayChats = (searchQuery) => {
|
||||
$("#select_chat_div").empty(); // Clear the current chats before appending filtered chats
|
||||
|
||||
if (selected_group) {
|
||||
template.find('.avatar img').replaceWith(getGroupAvatar(group));
|
||||
}
|
||||
const filteredData = data.filter(chat => {
|
||||
const fileName = chat['file_name'];
|
||||
const chatContent = rawChats[fileName];
|
||||
|
||||
$("#select_chat_div").append(template);
|
||||
return chatContent && Object.values(chatContent).some(message => message.mes.toLowerCase().includes(searchQuery.toLowerCase()));
|
||||
});
|
||||
|
||||
if (currentChat === fileName.toString().replace(".jsonl", "")) {
|
||||
$("#select_chat_div").find(".select_chat_block:last").attr("highlight", true);
|
||||
console.log(filteredData);
|
||||
for (const key in filteredData) {
|
||||
let strlen = 300;
|
||||
let mes = filteredData[key]["mes"];
|
||||
|
||||
if (mes !== undefined) {
|
||||
if (mes.length > strlen) {
|
||||
mes = "..." + mes.substring(mes.length - strlen);
|
||||
}
|
||||
const chat_items = data[key]["chat_items"];
|
||||
const file_size = data[key]["file_size"];
|
||||
const fileName = data[key]['file_name'];
|
||||
const timestamp = timestampToMoment(data[key]['last_mes']).format('LL LT');
|
||||
const template = $('#past_chat_template .select_chat_block_wrapper').clone();
|
||||
template.find('.select_chat_block').attr('file_name', fileName);
|
||||
template.find('.avatar img').attr('src', avatarImg);
|
||||
template.find('.select_chat_block_filename').text(fileName);
|
||||
template.find('.chat_file_size').text(" (" + file_size + ")");
|
||||
template.find('.chat_messages_num').text(" (" + chat_items + " messages)");
|
||||
template.find('.select_chat_block_mes').text(mes);
|
||||
template.find('.PastChat_cross').attr('file_name', fileName);
|
||||
template.find('.chat_messages_date').text(timestamp);
|
||||
|
||||
if (selected_group) {
|
||||
template.find('.avatar img').replaceWith(getGroupAvatar(group));
|
||||
}
|
||||
|
||||
$("#select_chat_div").append(template);
|
||||
|
||||
if (currentChat === fileName.toString().replace(".jsonl", "")) {
|
||||
$("#select_chat_div").find(".select_chat_block:last").attr("highlight", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
displayChats(''); // Display all by default
|
||||
|
||||
const debouncedDisplay = debounce((searchQuery) => {
|
||||
displayChats(searchQuery);
|
||||
}, 300);
|
||||
|
||||
// Define the search input listener
|
||||
$("#select_chat_search").on("input", function () {
|
||||
const searchQuery = $(this).val();
|
||||
debouncedDisplay(searchQuery);
|
||||
});
|
||||
}
|
||||
|
||||
//************************************************************
|
||||
@@ -5736,11 +5868,11 @@ async function getStatusNovel() {
|
||||
|
||||
function selectRightMenuWithAnimation(selectedMenuId) {
|
||||
const displayModes = {
|
||||
'rm_info_block': 'flex',
|
||||
'rm_group_chats_block': 'flex',
|
||||
'rm_api_block': 'grid',
|
||||
'rm_characters_block': 'flex',
|
||||
};
|
||||
$('#hideCharPanelAvatarButton').toggle(selectedMenuId === 'rm_ch_create_block');
|
||||
document.querySelectorAll('#right-nav-panel .right_menu').forEach((menu) => {
|
||||
$(menu).css('display', 'none');
|
||||
|
||||
@@ -5794,7 +5926,6 @@ function select_rm_info(type, charId, previousCharId = null) {
|
||||
toastr.success(`Character Imported: ${displayName}`);
|
||||
}
|
||||
|
||||
getCharacters();
|
||||
selectRightMenuWithAnimation('rm_characters_block');
|
||||
|
||||
setTimeout(function () {
|
||||
@@ -6202,10 +6333,10 @@ function hideSwipeButtons() {
|
||||
|
||||
async function saveMetadata() {
|
||||
if (selected_group) {
|
||||
await editGroup(selected_group, true, false);
|
||||
await editGroup(selected_group, false, false);
|
||||
}
|
||||
else {
|
||||
await saveChat();
|
||||
saveChatDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6609,12 +6740,8 @@ async function createOrEditCharacter(e) {
|
||||
createTagMapFromList("#tagList", html);
|
||||
await getCharacters();
|
||||
|
||||
$("#rm_info_block").transition({ opacity: 0, duration: 0 });
|
||||
var $prev_img = $("#avatar_div_div").clone();
|
||||
$("#rm_info_avatar").append($prev_img);
|
||||
select_rm_info(`char_create`, html, oldSelectedChar);
|
||||
|
||||
$("#rm_info_block").transition({ opacity: 1.0, duration: 2000 });
|
||||
crop_data = undefined;
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
@@ -6703,7 +6830,8 @@ async function createOrEditCharacter(e) {
|
||||
}
|
||||
}
|
||||
$("#create_button").removeAttr("disabled");
|
||||
await getCharacters();
|
||||
|
||||
await getOneCharacter(formData.get('avatar_url'));
|
||||
|
||||
$("#add_avatar_button").replaceWith(
|
||||
$("#add_avatar_button").val("").clone(true)
|
||||
@@ -7098,6 +7226,11 @@ function connectAPISlash(_, text) {
|
||||
source: 'openrouter',
|
||||
button: '#api_button_openai',
|
||||
},
|
||||
'ai21': {
|
||||
selected: 'openai',
|
||||
source: 'ai21',
|
||||
button: '#api_button_openai',
|
||||
}
|
||||
};
|
||||
|
||||
const apiConfig = apiMap[text];
|
||||
@@ -7160,12 +7293,6 @@ function importCharacter(file) {
|
||||
|
||||
if (data.file_name !== undefined) {
|
||||
$('#character_search_bar').val('').trigger('input');
|
||||
$("#rm_info_block").transition({ opacity: 0, duration: 0 });
|
||||
var $prev_img = $("#avatar_div_div").clone();
|
||||
$prev_img
|
||||
.children("img")
|
||||
.attr("src", "characters/" + data.file_name + ".png");
|
||||
$("#rm_info_avatar").append($prev_img);
|
||||
|
||||
let oldSelectedChar = null;
|
||||
if (this_chid != undefined && this_chid != "invalid-safety-id") {
|
||||
@@ -7180,7 +7307,6 @@ function importCharacter(file) {
|
||||
let importedCharacter = currentContext.characters.find(character => character.avatar === avatarFileName);
|
||||
await importTags(importedCharacter);
|
||||
}
|
||||
$("#rm_info_block").transition({ opacity: 1, duration: 1000 });
|
||||
}
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
@@ -7323,7 +7449,7 @@ $(document).ready(function () {
|
||||
}
|
||||
|
||||
registerSlashCommand('dupe', DupeChar, [], "– duplicates the currently selected character", true, true);
|
||||
registerSlashCommand('api', connectAPISlash, [], "(kobold, horde, novel, ooba, oai, claude, windowai) – connect to an API", true, true);
|
||||
registerSlashCommand('api', connectAPISlash, [], "(kobold, horde, novel, ooba, oai, claude, windowai, ai21) – connect to an API", true, true);
|
||||
registerSlashCommand('impersonate', doImpersonate, ['imp'], "- calls an impersonation response", true, true);
|
||||
registerSlashCommand('delchat', doDeleteChat, [], "- deletes the current chat", true, true);
|
||||
registerSlashCommand('closechat', doCloseChat, [], "- closes the current chat", true, true);
|
||||
@@ -7437,7 +7563,7 @@ $(document).ready(function () {
|
||||
$("#character_search_bar").val("").trigger("input");
|
||||
});
|
||||
|
||||
$(document).on("click", ".character_select", function() {
|
||||
$(document).on("click", ".character_select", function () {
|
||||
const id = $(this).attr("chid");
|
||||
selectCharacterById(id);
|
||||
});
|
||||
@@ -7648,7 +7774,7 @@ $(document).ready(function () {
|
||||
setTimeout(function () {
|
||||
$("#option_select_chat").click();
|
||||
$("#options").hide();
|
||||
}, 200);
|
||||
}, 2000);
|
||||
}
|
||||
if (popup_type == "del_ch") {
|
||||
const deleteChats = !!$("#del_char_checkbox").prop("checked");
|
||||
@@ -7747,11 +7873,6 @@ $(document).ready(function () {
|
||||
);
|
||||
});
|
||||
|
||||
$("#rm_info_button").on('click', function () {
|
||||
$("#rm_info_avatar").html("");
|
||||
select_rm_characters();
|
||||
});
|
||||
|
||||
//////// OPTIMIZED ALL CHAR CREATION/EDITING TEXTAREA LISTENERS ///////////////
|
||||
|
||||
$("#character_name_pole").on("input", function () {
|
||||
@@ -7926,7 +8047,9 @@ $(document).ready(function () {
|
||||
|
||||
$("#use-mancer-api-checkbox").on("change", function (e) {
|
||||
const enabled = $("#use-mancer-api-checkbox").prop("checked");
|
||||
$("#mancer-api-ui").toggle(enabled);
|
||||
$("#mancer_api_subpanel").toggle(enabled);
|
||||
$("#tgwebui_api_subpanel").toggle(!enabled);
|
||||
|
||||
api_use_mancer_webui = enabled;
|
||||
saveSettingsDebounced();
|
||||
getStatus();
|
||||
@@ -7934,8 +8057,9 @@ $(document).ready(function () {
|
||||
|
||||
$("#api_button_textgenerationwebui").click(async function (e) {
|
||||
e.stopPropagation();
|
||||
if ($("#textgenerationwebui_api_url_text").val() != "") {
|
||||
let value = formatTextGenURL($("#textgenerationwebui_api_url_text").val().trim(), api_use_mancer_webui);
|
||||
const url_source = api_use_mancer_webui ? "#mancer_api_url_text" : "#textgenerationwebui_api_url_text";
|
||||
if ($(url_source).val() != "") {
|
||||
let value = formatTextGenURL($(url_source).val().trim(), api_use_mancer_webui);
|
||||
if (!value) {
|
||||
callPopup("Please enter a valid URL.<br/>WebUI URLs should end with <tt>/api</tt><br/>Enable 'Relaxed API URLs' to allow other paths.", 'text');
|
||||
return;
|
||||
@@ -7946,9 +8070,13 @@ $(document).ready(function () {
|
||||
await writeSecret(SECRET_KEYS.MANCER, mancer_key);
|
||||
}
|
||||
|
||||
$("#textgenerationwebui_api_url_text").val(value);
|
||||
$(url_source).val(value);
|
||||
$("#api_loading_textgenerationwebui").css("display", "inline-block");
|
||||
$("#api_button_textgenerationwebui").css("display", "none");
|
||||
|
||||
if (api_use_mancer_webui) {
|
||||
textgenerationwebui_settings.streaming_url = value.replace("http", "ws") + "/v1/stream";
|
||||
}
|
||||
api_server_textgenerationwebui = value;
|
||||
main_api = "textgenerationwebui";
|
||||
saveSettingsDebounced();
|
||||
@@ -8501,8 +8629,10 @@ $(document).ready(function () {
|
||||
|
||||
$(document).on("click", ".mes_edit_delete", async function (event, customData) {
|
||||
const fromSlashCommand = customData?.fromSlashCommand || false;
|
||||
const swipeExists = (!chat[this_edit_mes_id].swipes || chat[this_edit_mes_id].swipes.length <= 1 || chat.is_user || parseInt(this_edit_mes_id) !== chat.length - 1);
|
||||
if (power_user.confirm_message_delete && fromSlashCommand !== true) {
|
||||
const confirmation = await callPopup("Are you sure you want to delete this message?", 'confirm');
|
||||
const confirmation = swipeExists ? await callPopup("Are you sure you want to delete this message?", 'confirm')
|
||||
: await callPopup("<h3>Delete this...</h3> <select id='del_type'><option value='swipe'>Swipe</option><option value='message'>Message</option></select>", 'confirm')
|
||||
if (!confirmation) {
|
||||
return;
|
||||
}
|
||||
@@ -8514,10 +8644,21 @@ $(document).ready(function () {
|
||||
return;
|
||||
}
|
||||
|
||||
chat.splice(this_edit_mes_id, 1);
|
||||
if ($('#del_type').val() === 'swipe') {
|
||||
const swipe_id = chat[this_edit_mes_id]['swipe_id'];
|
||||
chat[this_edit_mes_id]['swipes'].splice(swipe_id, 1);
|
||||
if (swipe_id > 0) {
|
||||
$('.swipe_left:last').click();
|
||||
} else {
|
||||
$('.swipe_right:last').click()
|
||||
}
|
||||
} else {
|
||||
chat.splice(this_edit_mes_id, 1);
|
||||
mes.remove();
|
||||
count_view_mes--;
|
||||
}
|
||||
|
||||
this_edit_mes_id = undefined;
|
||||
mes.remove();
|
||||
count_view_mes--;
|
||||
|
||||
updateViewMessageIds();
|
||||
saveChatConditional();
|
||||
@@ -9155,8 +9296,7 @@ $(document).ready(function () {
|
||||
doCharListDisplaySwitch();
|
||||
});
|
||||
|
||||
$("#hideCharPanelAvatarButton").on('click', () => {
|
||||
$("#hideCharPanelAvatarButton").hide().on('click', () => {
|
||||
$('#avatar-and-name-block').slideToggle()
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import {callPopup, event_types, eventSource, is_send_press, main_api, substituteParams} from "../script.js";
|
||||
"use strict";
|
||||
|
||||
import { callPopup, event_types, eventSource, is_send_press, main_api, substituteParams } from "../script.js";
|
||||
import { is_group_generating } from "./group-chats.js";
|
||||
import {TokenHandler} from "./openai.js";
|
||||
import {power_user} from "./power-user.js";
|
||||
import { debounce, getSortableDelay, waitUntilCondition } from "./utils.js";
|
||||
import { TokenHandler } from "./openai.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import { debounce, waitUntilCondition } from "./utils.js";
|
||||
|
||||
function debouncePromise(func, delay) {
|
||||
let timeoutId;
|
||||
@@ -70,7 +72,7 @@ class Prompt {
|
||||
* @param {string} param0.name - The name of the prompt.
|
||||
* @param {boolean} param0.system_prompt - Indicates if the prompt is a system prompt.
|
||||
*/
|
||||
constructor({identifier, role, content, name, system_prompt} = {}) {
|
||||
constructor({ identifier, role, content, name, system_prompt } = {}) {
|
||||
this.identifier = identifier;
|
||||
this.role = role;
|
||||
this.content = content;
|
||||
@@ -101,8 +103,8 @@ class PromptCollection {
|
||||
* @throws Will throw an error if one or more instances are not of the Prompt class.
|
||||
*/
|
||||
checkPromptInstance(...prompts) {
|
||||
for(let prompt of prompts) {
|
||||
if(!(prompt instanceof Prompt)) {
|
||||
for (let prompt of prompts) {
|
||||
if (!(prompt instanceof Prompt)) {
|
||||
throw new Error('Only Prompt instances can be added to PromptCollection');
|
||||
}
|
||||
}
|
||||
@@ -168,7 +170,11 @@ function PromptManagerModule() {
|
||||
listIdentifier: '',
|
||||
listItemTemplateIdentifier: '',
|
||||
toggleDisabled: [],
|
||||
draggable: true,
|
||||
promptOrder: {
|
||||
strategy: 'global',
|
||||
dummyId: 100000
|
||||
},
|
||||
sortableDelay: 30,
|
||||
warningTokenThreshold: 1500,
|
||||
dangerTokenThreshold: 500,
|
||||
defaultPrompts: {
|
||||
@@ -246,7 +252,7 @@ function PromptManagerModule() {
|
||||
this.handleCharacterExport = () => { };
|
||||
|
||||
/** Character reset button click*/
|
||||
this.handleCharacterReset = () => {};
|
||||
this.handleCharacterReset = () => { };
|
||||
|
||||
/** Debounced version of render */
|
||||
this.renderDebounced = debounce(this.render.bind(this), 1000);
|
||||
@@ -267,6 +273,8 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
this.serviceSettings = serviceSettings;
|
||||
this.containerElement = document.getElementById(this.configuration.containerIdentifier);
|
||||
|
||||
if ('global' === this.configuration.promptOrder.strategy) this.activeCharacter = {id: this.configuration.promptOrder.dummyId};
|
||||
|
||||
this.sanitizeServiceSettings();
|
||||
|
||||
// Enable and disable prompts
|
||||
@@ -378,7 +386,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
|
||||
const prompt = this.getPromptById(promptID);
|
||||
|
||||
if (prompt){
|
||||
if (prompt) {
|
||||
this.appendPrompt(prompt, this.activeCharacter);
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
}
|
||||
@@ -386,7 +394,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
|
||||
// Delete selected prompt from list form and close edit form
|
||||
this.handleDeletePrompt = (event) => {
|
||||
const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
|
||||
const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
|
||||
const prompt = this.getPromptById(promptID);
|
||||
|
||||
if (prompt && true === this.isPromptDeletionAllowed(prompt)) {
|
||||
@@ -416,12 +424,26 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
|
||||
// Export all user prompts
|
||||
this.handleFullExport = () => {
|
||||
const exportPrompts = this.serviceSettings.prompts.reduce((userPrompts, prompt) => {
|
||||
const prompts = this.serviceSettings.prompts.reduce((userPrompts, prompt) => {
|
||||
if (false === prompt.system_prompt && false === prompt.marker) userPrompts.push(prompt);
|
||||
return userPrompts;
|
||||
}, []);
|
||||
|
||||
this.export({prompts: exportPrompts}, 'full', 'st-prompts');
|
||||
let promptOrder = [];
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId });
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
promptOrder = [];
|
||||
} else {
|
||||
throw new Error('Prompt order strategy not supported.')
|
||||
}
|
||||
|
||||
const exportPrompts = {
|
||||
prompts: prompts,
|
||||
prompt_order: promptOrder
|
||||
}
|
||||
|
||||
this.export(exportPrompts, 'full', 'st-prompts');
|
||||
}
|
||||
|
||||
// Export user prompts and order for this character
|
||||
@@ -472,10 +494,10 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
});
|
||||
});
|
||||
|
||||
fileOpener.click();
|
||||
});
|
||||
fileOpener.click();
|
||||
});
|
||||
}
|
||||
|
||||
// Restore default state of a characters prompt order
|
||||
@@ -538,7 +560,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_save').addEventListener('click', this.handleSavePrompt);
|
||||
document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_reset').addEventListener('click', this.handleResetPrompt);
|
||||
|
||||
const closeAndClearPopup = () => {
|
||||
const closeAndClearPopup = () => {
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.clearInspectForm();
|
||||
@@ -554,7 +576,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
this.saveServiceSettings().then(() => {
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.renderDebounced()
|
||||
this.renderDebounced();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -572,7 +594,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
PromptManagerModule.prototype.render = function (afterTryGenerate = true) {
|
||||
if (main_api !== 'openai') return;
|
||||
|
||||
if (null === this.activeCharacter) return;
|
||||
if ('character' === this.configuration.promptOrder.strategy && null === this.activeCharacter) return;
|
||||
this.error = null;
|
||||
|
||||
waitUntilCondition(() => !is_send_press && !is_group_generating, 1024 * 1024, 100).then(() => {
|
||||
@@ -586,6 +608,11 @@ PromptManagerModule.prototype.render = function (afterTryGenerate = true) {
|
||||
this.renderPromptManagerListItems()
|
||||
this.makeDraggable();
|
||||
this.profileEnd('render');
|
||||
}).catch(error => {
|
||||
this.log('Error caught during render: ' + error);
|
||||
this.renderPromptManager();
|
||||
this.renderPromptManagerListItems()
|
||||
this.makeDraggable();
|
||||
});
|
||||
} else {
|
||||
// Executed during live communication
|
||||
@@ -634,7 +661,7 @@ PromptManagerModule.prototype.updatePrompts = function (prompts) {
|
||||
})
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.getTokenHandler = function() {
|
||||
PromptManagerModule.prototype.getTokenHandler = function () {
|
||||
return this.tokenHandler;
|
||||
}
|
||||
|
||||
@@ -648,7 +675,7 @@ PromptManagerModule.prototype.appendPrompt = function (prompt, character) {
|
||||
const promptOrder = this.getPromptOrderForCharacter(character);
|
||||
const index = promptOrder.findIndex(entry => entry.identifier === prompt.identifier);
|
||||
|
||||
if (-1 === index) promptOrder.push({identifier: prompt.identifier, enabled: false});
|
||||
if (-1 === index) promptOrder.push({ identifier: prompt.identifier, enabled: false });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -694,6 +721,13 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () {
|
||||
this.serviceSettings.prompts = this.serviceSettings.prompts ?? [];
|
||||
this.serviceSettings.prompt_order = this.serviceSettings.prompt_order ?? [];
|
||||
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
const dummyCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
const promptOrder = this.getPromptOrderForCharacter(dummyCharacter);
|
||||
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(dummyCharacter, promptManagerDefaultPromptOrder);
|
||||
}
|
||||
|
||||
// Check whether the referenced prompts are present.
|
||||
this.serviceSettings.prompts.length === 0
|
||||
? this.setPrompts(chatCompletionDefaultPrompts.prompts)
|
||||
@@ -704,11 +738,11 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () {
|
||||
|
||||
if (this.activeCharacter) {
|
||||
const promptReferences = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
for(let i = promptReferences.length - 1; i >= 0; i--) {
|
||||
const reference = promptReferences[i];
|
||||
if(-1 === this.serviceSettings.prompts.findIndex(prompt => prompt.identifier === reference.identifier)) {
|
||||
for (let i = promptReferences.length - 1; i >= 0; i--) {
|
||||
const reference = promptReferences[i];
|
||||
if (-1 === this.serviceSettings.prompts.findIndex(prompt => prompt.identifier === reference.identifier)) {
|
||||
promptReferences.splice(i, 1);
|
||||
this.log('Removed unused reference: ' + reference.identifier);
|
||||
this.log('Removed unused reference: ' + reference.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -720,11 +754,11 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () {
|
||||
*
|
||||
* @param prompts
|
||||
*/
|
||||
PromptManagerModule.prototype.checkForMissingPrompts = function(prompts) {
|
||||
const defaultPromptIdentifiers = chatCompletionDefaultPrompts.prompts.reduce((list, prompt) => { list.push(prompt.identifier); return list;}, []);
|
||||
PromptManagerModule.prototype.checkForMissingPrompts = function (prompts) {
|
||||
const defaultPromptIdentifiers = chatCompletionDefaultPrompts.prompts.reduce((list, prompt) => { list.push(prompt.identifier); return list; }, []);
|
||||
|
||||
const missingIdentifiers = defaultPromptIdentifiers.filter(identifier =>
|
||||
!prompts.some(prompt =>prompt.identifier === identifier)
|
||||
!prompts.some(prompt => prompt.identifier === identifier)
|
||||
);
|
||||
|
||||
missingIdentifiers.forEach(identifier => {
|
||||
@@ -775,9 +809,10 @@ PromptManagerModule.prototype.isPromptToggleAllowed = function (prompt) {
|
||||
/**
|
||||
* Handle the deletion of a character by removing their prompt list and nullifying the active character if it was the one deleted.
|
||||
* @param {object} event - The event object containing the character's ID.
|
||||
* @returns boolean
|
||||
* @returns void
|
||||
*/
|
||||
PromptManagerModule.prototype.handleCharacterDeleted = function (event) {
|
||||
if ('global' === this.configuration.promptOrder.strategy) return;
|
||||
this.removePromptOrderForCharacter(this.activeCharacter);
|
||||
if (this.activeCharacter.id === event.detail.id) this.activeCharacter = null;
|
||||
}
|
||||
@@ -788,12 +823,19 @@ PromptManagerModule.prototype.handleCharacterDeleted = function (event) {
|
||||
* @returns {void}
|
||||
*/
|
||||
PromptManagerModule.prototype.handleCharacterSelected = function (event) {
|
||||
this.activeCharacter = {id: event.detail.id, ...event.detail.character};
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
console.log('FOO')
|
||||
this.activeCharacter = { id: event.detail.id, ...event.detail.character };
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
|
||||
// ToDo: These should be passed as parameter or attached to the manager as a set of default options.
|
||||
// Set default prompts and order for character.
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder);
|
||||
// ToDo: These should be passed as parameter or attached to the manager as a set of default options.
|
||||
// Set default prompts and order for character.
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder);
|
||||
} else {
|
||||
throw new Error('Unsupported prompt order mode.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -802,7 +844,13 @@ PromptManagerModule.prototype.handleCharacterSelected = function (event) {
|
||||
* @param event
|
||||
*/
|
||||
PromptManagerModule.prototype.handleCharacterUpdated = function (event) {
|
||||
this.activeCharacter = {id: event.detail.id, ...event.detail.character};
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = { id: event.detail.id, ...event.detail.character };
|
||||
} else {
|
||||
throw new Error('Prompt order strategy not supported.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -811,11 +859,17 @@ PromptManagerModule.prototype.handleCharacterUpdated = function (event) {
|
||||
* @param event
|
||||
*/
|
||||
PromptManagerModule.prototype.handleGroupSelected = function (event) {
|
||||
const characterDummy = {id: event.detail.id, group: event.detail.group};
|
||||
this.activeCharacter = characterDummy;
|
||||
const promptOrder = this.getPromptOrderForCharacter(characterDummy);
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
const characterDummy = { id: event.detail.id, group: event.detail.group };
|
||||
this.activeCharacter = characterDummy;
|
||||
const promptOrder = this.getPromptOrderForCharacter(characterDummy);
|
||||
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(characterDummy, promptManagerDefaultPromptOrder)
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(characterDummy, promptManagerDefaultPromptOrder)
|
||||
} else {
|
||||
throw new Error('Prompt order strategy not supported.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -823,7 +877,7 @@ PromptManagerModule.prototype.handleGroupSelected = function (event) {
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
PromptManagerModule.prototype.getActiveGroupCharacters = function() {
|
||||
PromptManagerModule.prototype.getActiveGroupCharacters = function () {
|
||||
// ToDo: Ideally, this should return the actual characters.
|
||||
return (this.activeCharacter?.group?.members || []).map(member => member && member.substring(0, member.lastIndexOf('.')));
|
||||
}
|
||||
@@ -937,7 +991,7 @@ PromptManagerModule.prototype.preparePrompt = function (prompt, original = null)
|
||||
* and handle input events to update the prompt content.
|
||||
*
|
||||
*/
|
||||
PromptManagerModule.prototype.createQuickEdit = function(identifier, title) {
|
||||
PromptManagerModule.prototype.createQuickEdit = function (identifier, title) {
|
||||
const prompt = this.getPromptById(identifier);
|
||||
const textareaIdentifier = `${identifier}_prompt_quick_edit_textarea`;
|
||||
const html = `<div class="range-block m-t-1">
|
||||
@@ -961,7 +1015,7 @@ PromptManagerModule.prototype.createQuickEdit = function(identifier, title) {
|
||||
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.updateQuickEdit = function(identifier, prompt) {
|
||||
PromptManagerModule.prototype.updateQuickEdit = function (identifier, prompt) {
|
||||
const textarea = document.getElementById(`${identifier}_prompt_quick_edit_textarea`);
|
||||
textarea.value = prompt.content;
|
||||
}
|
||||
@@ -973,13 +1027,13 @@ PromptManagerModule.prototype.updateQuickEdit = function(identifier, prompt) {
|
||||
* @param name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PromptManagerModule.prototype.isValidName = function(name) {
|
||||
PromptManagerModule.prototype.isValidName = function (name) {
|
||||
const regex = /^[a-zA-Z0-9_]{1,64}$/;
|
||||
|
||||
return regex.test(name);
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.sanitizeName = function(name) {
|
||||
PromptManagerModule.prototype.sanitizeName = function (name) {
|
||||
return name.replace(/[^a-zA-Z0-9_]/g, '_').substring(0, 64);
|
||||
}
|
||||
|
||||
@@ -1066,7 +1120,7 @@ PromptManagerModule.prototype.clearEditForm = function () {
|
||||
roleField.disabled = false;
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.clearInspectForm = function() {
|
||||
PromptManagerModule.prototype.clearInspectForm = function () {
|
||||
const inspectArea = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_inspect');
|
||||
inspectArea.style.display = 'none';
|
||||
const messageList = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_inspect_list');
|
||||
@@ -1105,12 +1159,11 @@ PromptManagerModule.prototype.setMessages = function (messages) {
|
||||
*
|
||||
* @param {ChatCompletion} chatCompletion
|
||||
*/
|
||||
PromptManagerModule.prototype.setChatCompletion = function(chatCompletion) {
|
||||
PromptManagerModule.prototype.setChatCompletion = function (chatCompletion) {
|
||||
const messages = chatCompletion.getMessages();
|
||||
|
||||
this.setMessages(messages);
|
||||
this.populateTokenCounts(messages);
|
||||
this.populateLegacyTokenCounts(messages);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1118,7 +1171,7 @@ PromptManagerModule.prototype.setChatCompletion = function(chatCompletion) {
|
||||
*
|
||||
* @param {MessageCollection} messages
|
||||
*/
|
||||
PromptManagerModule.prototype.populateTokenCounts = function(messages) {
|
||||
PromptManagerModule.prototype.populateTokenCounts = function (messages) {
|
||||
this.tokenHandler.resetCounts();
|
||||
const counts = this.tokenHandler.getCounts();
|
||||
messages.getCollection().forEach(message => {
|
||||
@@ -1137,10 +1190,10 @@ PromptManagerModule.prototype.populateTokenCounts = function(messages) {
|
||||
*
|
||||
* @param {MessageCollection} messages
|
||||
*/
|
||||
PromptManagerModule.prototype.populateLegacyTokenCounts = function(messages) {
|
||||
PromptManagerModule.prototype.populateLegacyTokenCounts = function (messages) {
|
||||
// Update general token counts
|
||||
const chatHistory = messages.getItemByIdentifier('chatHistory');
|
||||
const startChat = chatHistory?.getCollection()[0].getTokens() || 0;
|
||||
const startChat = chatHistory?.getCollection()[0]?.getTokens() || 0;
|
||||
const continueNudge = chatHistory?.getCollection().find(message => message.identifier === 'continueNudge')?.getTokens() || 0;
|
||||
|
||||
this.tokenHandler.counts = {
|
||||
@@ -1201,8 +1254,8 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
</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>
|
||||
@@ -1225,10 +1278,14 @@ 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>
|
||||
<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" title="Export prompts for this character, including their order."></span>
|
||||
</div>
|
||||
${'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"
|
||||
title="Export prompts for this character, including their order."></span>
|
||||
</div>` }
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1238,7 +1295,7 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
let exportPopper = Popper.createPopper(
|
||||
document.getElementById('prompt-manager-export'),
|
||||
document.getElementById('prompt-manager-export-format-popup'),
|
||||
{placement: 'bottom'}
|
||||
{ placement: 'bottom' }
|
||||
);
|
||||
|
||||
const showExportSelection = () => {
|
||||
@@ -1254,17 +1311,49 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
footerDiv.querySelector('#prompt-manager-import').addEventListener('click', this.handleImport);
|
||||
footerDiv.querySelector('#prompt-manager-export').addEventListener('click', showExportSelection);
|
||||
rangeBlockDiv.querySelector('.export-promptmanager-prompts-full').addEventListener('click', this.handleFullExport);
|
||||
rangeBlockDiv.querySelector('.export-promptmanager-prompts-character').addEventListener('click', this.handleCharacterExport);
|
||||
rangeBlockDiv.querySelector('.export-promptmanager-prompts-character')?.addEventListener('click', this.handleCharacterExport);
|
||||
|
||||
const quickEditContainer = document.getElementById('quick-edit-container');
|
||||
const heights = this.saveTextAreaHeights(quickEditContainer);
|
||||
quickEditContainer.innerHTML = '';
|
||||
|
||||
this.createQuickEdit('jailbreak', 'Jailbreak');
|
||||
this.createQuickEdit('nsfw', 'NSFW');
|
||||
this.createQuickEdit('main', 'Main');
|
||||
|
||||
this.restoreTextAreaHeights(quickEditContainer, heights);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores the height of each textarea in the container
|
||||
* @param container The container to search for textareas
|
||||
* @param heights An object with textarea ids as keys and heights as values
|
||||
*/
|
||||
PromptManagerModule.prototype.restoreTextAreaHeights = function(container, heights) {
|
||||
if (Object.keys(heights).length === 0) return;
|
||||
|
||||
$(container).find('textarea').each(function () {
|
||||
const height = heights[this.id];
|
||||
if (height > 0) $(this).height(height);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current height of each textarea in the container
|
||||
* @param container The container to search for textareas
|
||||
* @returns {{}} An object with textarea ids as keys and heights as values
|
||||
*/
|
||||
PromptManagerModule.prototype.saveTextAreaHeights = function(container) {
|
||||
const heights = {};
|
||||
|
||||
$(container).find('textarea').each(function () {
|
||||
heights[this.id] = $(this).height();
|
||||
});
|
||||
|
||||
return heights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties, then re-assembles the prompt list
|
||||
*/
|
||||
@@ -1274,7 +1363,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
|
||||
const promptManagerList = this.listElement;
|
||||
promptManagerList.innerHTML = '';
|
||||
|
||||
const {prefix} = this.configuration;
|
||||
const { prefix } = this.configuration;
|
||||
|
||||
let listItemHtml = `
|
||||
<li class="${prefix}prompt_manager_list_head">
|
||||
@@ -1301,7 +1390,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
|
||||
let warningTitle = '';
|
||||
|
||||
const tokenBudget = this.serviceSettings.openai_max_context - this.serviceSettings.openai_max_tokens;
|
||||
if ( this.tokenUsage > tokenBudget * 0.8 &&
|
||||
if (this.tokenUsage > tokenBudget * 0.8 &&
|
||||
'chatHistory' === prompt.identifier) {
|
||||
const warningThreshold = this.configuration.warningTokenThreshold;
|
||||
const dangerThreshold = this.configuration.dangerTokenThreshold;
|
||||
@@ -1350,7 +1439,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
|
||||
${prompt.marker ? '<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">
|
||||
@@ -1400,7 +1489,7 @@ PromptManagerModule.prototype.export = function (data, type, name = 'export') {
|
||||
};
|
||||
|
||||
const serializedObject = JSON.stringify(promptExport);
|
||||
const blob = new Blob([serializedObject], {type: "application/json"});
|
||||
const blob = new Blob([serializedObject], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = url;
|
||||
@@ -1451,10 +1540,19 @@ PromptManagerModule.prototype.import = function (importData) {
|
||||
this.setPrompts(prompts);
|
||||
this.log('Prompt import succeeded');
|
||||
|
||||
if ('character' === importData.type) {
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
let promptOrder = [];
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
const promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId });
|
||||
Object.assign(promptOrder, importData.data.prompt_order);
|
||||
this.log(`Prompt order import for character ${this.activeCharacter.name} completed`);
|
||||
this.log(`Prompt order import succeeded`);
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
if ('character' === importData.type) {
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
Object.assign(promptOrder, importData.data.prompt_order);
|
||||
this.log(`Prompt order import for character ${this.activeCharacter.name} succeeded`);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Prompt order strategy not supported.')
|
||||
}
|
||||
|
||||
toastr.success('Prompt import complete.');
|
||||
@@ -1468,7 +1566,7 @@ PromptManagerModule.prototype.import = function (importData) {
|
||||
* @param object
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PromptManagerModule.prototype.validateObject = function(controlObj, object) {
|
||||
PromptManagerModule.prototype.validateObject = function (controlObj, object) {
|
||||
for (let key in controlObj) {
|
||||
if (!object.hasOwnProperty(key)) {
|
||||
if (controlObj[key] === null) continue;
|
||||
@@ -1491,7 +1589,7 @@ PromptManagerModule.prototype.validateObject = function(controlObj, object) {
|
||||
*
|
||||
* @returns {`${string}_${string}_${string}`}
|
||||
*/
|
||||
PromptManagerModule.prototype.getFormattedDate = function() {
|
||||
PromptManagerModule.prototype.getFormattedDate = function () {
|
||||
const date = new Date();
|
||||
let month = String(date.getMonth() + 1);
|
||||
let day = String(date.getDate());
|
||||
@@ -1511,11 +1609,11 @@ PromptManagerModule.prototype.getFormattedDate = function() {
|
||||
*/
|
||||
PromptManagerModule.prototype.makeDraggable = function () {
|
||||
$(`#${this.configuration.prefix}prompt_manager_list`).sortable({
|
||||
delay: getSortableDelay(),
|
||||
delay: this.configuration.sortableDelay,
|
||||
items: `.${this.configuration.prefix}prompt_manager_prompt_draggable`,
|
||||
update: ( event, ui ) => {
|
||||
update: (event, ui) => {
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
const promptListElement = $(`#${this.configuration.prefix}prompt_manager_list`).sortable('toArray', {attribute: 'data-pm-identifier'});
|
||||
const promptListElement = $(`#${this.configuration.prefix}prompt_manager_list`).sortable('toArray', { attribute: 'data-pm-identifier' });
|
||||
const idToObjectMap = new Map(promptOrder.map(prompt => [prompt.identifier, prompt]));
|
||||
const updatedPromptOrder = promptListElement.map(identifier => idToObjectMap.get(identifier));
|
||||
|
||||
@@ -1525,7 +1623,8 @@ PromptManagerModule.prototype.makeDraggable = function () {
|
||||
this.log(`Prompt order updated for ${this.activeCharacter.name}.`);
|
||||
|
||||
this.saveServiceSettings();
|
||||
}});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1536,7 +1635,7 @@ PromptManagerModule.prototype.showPopup = function (area = 'edit') {
|
||||
const areaElement = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_' + area);
|
||||
areaElement.style.display = 'block';
|
||||
|
||||
$('#'+this.configuration.prefix +'prompt_manager_popup').first()
|
||||
$('#' + this.configuration.prefix + 'prompt_manager_popup').first()
|
||||
.slideDown(200, "swing")
|
||||
.addClass('openDrawer');
|
||||
}
|
||||
@@ -1546,7 +1645,7 @@ PromptManagerModule.prototype.showPopup = function (area = 'edit') {
|
||||
* @returns {void}
|
||||
*/
|
||||
PromptManagerModule.prototype.hidePopup = function () {
|
||||
$('#'+this.configuration.prefix +'prompt_manager_popup').first()
|
||||
$('#' + this.configuration.prefix + 'prompt_manager_popup').first()
|
||||
.slideUp(200, "swing")
|
||||
.removeClass('openDrawer');
|
||||
}
|
||||
|
@@ -33,12 +33,12 @@ import {
|
||||
} from "./power-user.js";
|
||||
|
||||
import { LoadLocal, SaveLocal, CheckLocal, LoadLocalBool } from "./f-localStorage.js";
|
||||
import { selected_group, is_group_generating, getGroupAvatar, groups } from "./group-chats.js";
|
||||
import { selected_group, is_group_generating, getGroupAvatar, groups, openGroupById } from "./group-chats.js";
|
||||
import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
} from "./secrets.js";
|
||||
import { sortByCssOrder, debounce, delay } from "./utils.js";
|
||||
import { debounce, delay } from "./utils.js";
|
||||
import { chat_completion_sources, oai_settings } from "./openai.js";
|
||||
|
||||
var NavToggle = document.getElementById("nav-toggle");
|
||||
@@ -346,7 +346,7 @@ export function RA_CountCharTokens() {
|
||||
* The character or group is selected (clicked) if it is found.
|
||||
*/
|
||||
async function RA_autoloadchat() {
|
||||
if (document.getElementById('CharID0') !== null) {
|
||||
if (document.querySelector('#rm_print_characters_block .character_select') !== null) {
|
||||
// active character is the name, we should look it up in the character list and get the id
|
||||
let active_character_id = Object.keys(characters).find(key => characters[key].avatar === active_character);
|
||||
|
||||
@@ -354,10 +354,8 @@ async function RA_autoloadchat() {
|
||||
selectCharacterById(String(active_character_id));
|
||||
}
|
||||
|
||||
let groupToAutoLoad = document.querySelector(`.group_select[grid="${active_group}"]`);
|
||||
|
||||
if (groupToAutoLoad != null) {
|
||||
$(groupToAutoLoad).click();
|
||||
if (active_group != null) {
|
||||
openGroupById(String(active_group));
|
||||
}
|
||||
|
||||
// if the character list hadn't been loaded yet, try again.
|
||||
@@ -395,17 +393,18 @@ export async function favsToHotswap() {
|
||||
slot.attr('grid', isGroup ? grid : '');
|
||||
slot.attr('chid', isCharacter ? chid : '');
|
||||
slot.data('id', isGroup ? grid : chid);
|
||||
slot.attr('title', '');
|
||||
|
||||
if (isGroup) {
|
||||
const group = groups.find(x => x.id === grid);
|
||||
const avatar = getGroupAvatar(group);
|
||||
$(slot).find('img').replaceWith(avatar);
|
||||
$(slot).attr('title', group.name);
|
||||
}
|
||||
|
||||
if (isCharacter) {
|
||||
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
|
||||
$(slot).find('img').attr('src', avatarUrl);
|
||||
$(slot).attr('title', entity.item.avatar);
|
||||
}
|
||||
|
||||
$(slot).css('cursor', 'pointer');
|
||||
@@ -478,6 +477,7 @@ function RA_autoconnect(PrevApi) {
|
||||
|| (secret_state[SECRET_KEYS.SCALE] && oai_settings.chat_completion_source == chat_completion_sources.SCALE)
|
||||
|| (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI)
|
||||
|| (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)
|
||||
|| (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21)
|
||||
) {
|
||||
$("#api_button_openai").click();
|
||||
}
|
||||
@@ -932,14 +932,16 @@ $("document").ready(function () {
|
||||
|
||||
// when a char is selected from the list, save their name as the auto-load character for next page load
|
||||
$(document).on("click", ".character_select", function () {
|
||||
setActiveCharacter($(this).find('.avatar').attr('title'));
|
||||
const characterId = $(this).find('.avatar').attr('title') || $(this).attr('title');
|
||||
setActiveCharacter(characterId);
|
||||
setActiveGroup(null);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).on("click", ".group_select", function () {
|
||||
const groupId = $(this).data('id') || $(this).attr('grid');
|
||||
setActiveCharacter(null);
|
||||
setActiveGroup($(this).data('id'));
|
||||
setActiveGroup(groupId);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -380,8 +380,7 @@ async function summarizeChatMain(context, force) {
|
||||
}
|
||||
|
||||
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary);
|
||||
const prompt = substituteParams(extension_settings.memory.prompt)
|
||||
.replace(/{{words}}/gi, extension_settings.memory.promptWords);
|
||||
const prompt = extension_settings.memory.prompt?.replace(/{{words}}/gi, extension_settings.memory.promptWords);
|
||||
|
||||
if (!prompt) {
|
||||
console.debug('Summarization prompt is empty. Skipping summarization.');
|
||||
|
@@ -72,20 +72,22 @@ function getTaskByIdRecurse(taskId, task) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function substituteParamsPrompts(content) {
|
||||
function substituteParamsPrompts(content, substituteGlobal) {
|
||||
content = content.replace(/{{objective}}/gi, currentObjective.description)
|
||||
content = content.replace(/{{task}}/gi, currentTask.description)
|
||||
if (currentTask.parent){
|
||||
content = content.replace(/{{parent}}/gi, currentTask.parent.description)
|
||||
}
|
||||
content = substituteParams(content)
|
||||
if (substituteGlobal) {
|
||||
content = substituteParams(content)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// Call Quiet Generate to create task list using character context, then convert to tasks. Should not be called much.
|
||||
async function generateTasks() {
|
||||
|
||||
const prompt = substituteParamsPrompts(objectivePrompts.createTask);
|
||||
const prompt = substituteParamsPrompts(objectivePrompts.createTask, false);
|
||||
console.log(`Generating tasks for objective with prompt`)
|
||||
toastr.info('Generating tasks for objective', 'Please wait...');
|
||||
const taskResponse = await generateQuietPrompt(prompt)
|
||||
@@ -128,7 +130,7 @@ async function checkTaskCompleted() {
|
||||
checkCounter = $('#objective-check-frequency').val()
|
||||
toastr.info("Checking for task completion.")
|
||||
|
||||
const prompt = substituteParamsPrompts(objectivePrompts.checkTaskCompleted);
|
||||
const prompt = substituteParamsPrompts(objectivePrompts.checkTaskCompleted, false);
|
||||
const taskResponse = (await generateQuietPrompt(prompt)).toLowerCase()
|
||||
|
||||
// Check response if task complete
|
||||
@@ -178,7 +180,7 @@ function setCurrentTask(taskId = null) {
|
||||
// Don't just check for a current task, check if it has data
|
||||
const description = currentTask.description || null;
|
||||
if (description) {
|
||||
const extensionPromptText = substituteParamsPrompts(objectivePrompts.currentTask);
|
||||
const extensionPromptText = substituteParamsPrompts(objectivePrompts.currentTask, true);
|
||||
|
||||
// Remove highlights
|
||||
$('.objective-task').css({'border-color':'','border-width':''})
|
||||
|
@@ -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");
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
isDataURL,
|
||||
createThumbnail,
|
||||
extractAllWords,
|
||||
saveBase64AsFile
|
||||
} from './utils.js';
|
||||
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap } from "./RossAscends-mods.js";
|
||||
import { loadMovingUIState, sortEntitiesList } from './power-user.js';
|
||||
@@ -63,8 +64,8 @@ import {
|
||||
getCropPopup,
|
||||
system_avatar,
|
||||
} from "../script.js";
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
|
||||
import { FilterHelper } from './filters.js';
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map, printTagFilters } from './tags.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
|
||||
export {
|
||||
selected_group,
|
||||
@@ -80,6 +81,7 @@ export {
|
||||
regenerateGroup,
|
||||
resetSelectedGroup,
|
||||
select_group_chats,
|
||||
getGroupChatNames,
|
||||
}
|
||||
|
||||
let is_group_generating = false; // Group generation flag
|
||||
@@ -175,6 +177,7 @@ export async function getGroupChat(groupId) {
|
||||
addOneMessage(mes);
|
||||
}
|
||||
}
|
||||
await saveGroupChat(groupId, false);
|
||||
}
|
||||
|
||||
if (group) {
|
||||
@@ -182,7 +185,6 @@ export async function getGroupChat(groupId) {
|
||||
updateChatMetadata(metadata, true);
|
||||
}
|
||||
|
||||
await saveGroupChat(groupId, true);
|
||||
eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId());
|
||||
}
|
||||
|
||||
@@ -308,6 +310,9 @@ async function getGroups() {
|
||||
|
||||
// Convert groups to new format
|
||||
for (const group of groups) {
|
||||
if (typeof group.id === 'number') {
|
||||
group.id = String(group.id);
|
||||
}
|
||||
if (group.disabled_members == undefined) {
|
||||
group.disabled_members = [];
|
||||
}
|
||||
@@ -333,25 +338,25 @@ async function getGroups() {
|
||||
}
|
||||
|
||||
export function getGroupBlock(group) {
|
||||
const template = $("#group_list_template .group_select").clone();
|
||||
template.data("id", group.id);
|
||||
template.attr("grid", group.id);
|
||||
template.find(".ch_name").html(group.name);
|
||||
template.find('.group_fav_icon').css("display", 'none');
|
||||
template.addClass(group.fav ? 'is_fav' : '');
|
||||
template.find(".ch_fav").val(group.fav);
|
||||
const template = $("#group_list_template .group_select").clone();
|
||||
template.data("id", group.id);
|
||||
template.attr("grid", group.id);
|
||||
template.find(".ch_name").text(group.name);
|
||||
template.find('.group_fav_icon').css("display", 'none');
|
||||
template.addClass(group.fav ? 'is_fav' : '');
|
||||
template.find(".ch_fav").val(group.fav);
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(group.id);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
// Display inline tags
|
||||
const tags = getTagsList(group.id);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
|
||||
const avatar = getGroupAvatar(group);
|
||||
if (avatar) {
|
||||
$(template).find(".avatar").replaceWith(avatar);
|
||||
}
|
||||
const avatar = getGroupAvatar(group);
|
||||
if (avatar) {
|
||||
$(template).find(".avatar").replaceWith(avatar);
|
||||
}
|
||||
|
||||
return template;
|
||||
return template;
|
||||
}
|
||||
|
||||
function updateGroupAvatar(group) {
|
||||
@@ -359,17 +364,26 @@ function updateGroupAvatar(group) {
|
||||
|
||||
$(".group_select").each(function () {
|
||||
if ($(this).data("id") == group.id) {
|
||||
$(this).find(".avatar").replaceWith(getGroupAvatar(group));
|
||||
$(this).find(".avatar").replaceWith(getGroupAvatar(group));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// check if isDataURLor if it's a valid local file url
|
||||
function isValidImageUrl(url) {
|
||||
// check if empty dict
|
||||
if (Object.keys(url).length === 0) {
|
||||
return false;
|
||||
}
|
||||
return isDataURL(url) || (url && url.startsWith("user"));
|
||||
}
|
||||
|
||||
function getGroupAvatar(group) {
|
||||
if (!group) {
|
||||
return $(`<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>`);
|
||||
}
|
||||
|
||||
@@ -405,6 +419,19 @@ function getGroupAvatar(group) {
|
||||
return groupAvatar;
|
||||
}
|
||||
|
||||
function getGroupChatNames(groupId) {
|
||||
const group = groups.find(x => x.id === groupId);
|
||||
|
||||
if (!group) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const names = [];
|
||||
for (const chatId of group.chats) {
|
||||
names.push(chatId);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
if (online_status === "no_connection") {
|
||||
@@ -802,10 +829,7 @@ async function deleteGroup(id) {
|
||||
printMessages();
|
||||
await getCharacters();
|
||||
|
||||
$("#rm_info_avatar").html("");
|
||||
$("#rm_info_block").transition({ opacity: 0, duration: 0 });
|
||||
select_rm_info("group_delete", id);
|
||||
$("#rm_info_block").transition({ opacity: 1.0, duration: 2000 });
|
||||
|
||||
$("#rm_button_selected_ch").children("h2").text('');
|
||||
setRightTabSelectedClass();
|
||||
@@ -957,6 +981,7 @@ function getGroupCharacters({ doFilter, onlyMembers } = {}) {
|
||||
}
|
||||
|
||||
function printGroupCandidates() {
|
||||
const storageKey = 'GroupCandidates_PerPage';
|
||||
$("#rm_group_add_members_pagination").pagination({
|
||||
dataSource: getGroupCharacters({ doFilter: true, onlyMembers: false }),
|
||||
pageSize: 5,
|
||||
@@ -967,6 +992,12 @@ function printGroupCandidates() {
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
showNavigator: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: Number(localStorage.getItem(storageKey)) || 5,
|
||||
sizeChangerOptions: [5, 10, 25, 50, 100, 200],
|
||||
afterSizeSelectorChange: function (e) {
|
||||
localStorage.setItem(storageKey, e.target.value);
|
||||
},
|
||||
callback: function (data) {
|
||||
$("#rm_group_add_members").empty();
|
||||
for (const i of data) {
|
||||
@@ -977,6 +1008,7 @@ function printGroupCandidates() {
|
||||
}
|
||||
|
||||
function printGroupMembers() {
|
||||
const storageKey = 'GroupMembers_PerPage';
|
||||
$("#rm_group_members_pagination").pagination({
|
||||
dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }),
|
||||
pageSize: 5,
|
||||
@@ -987,6 +1019,12 @@ function printGroupMembers() {
|
||||
prevText: '<',
|
||||
nextText: '>',
|
||||
showNavigator: true,
|
||||
showSizeChanger: true,
|
||||
pageSize: Number(localStorage.getItem(storageKey)) || 5,
|
||||
sizeChangerOptions: [5, 10, 25, 50, 100, 200],
|
||||
afterSizeSelectorChange: function (e) {
|
||||
localStorage.setItem(storageKey, e.target.value);
|
||||
},
|
||||
callback: function (data) {
|
||||
$("#rm_group_members").empty();
|
||||
for (const i of data) {
|
||||
@@ -1065,8 +1103,7 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
|
||||
setMenuType(!!group ? 'group_edit' : 'group_create');
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(group));
|
||||
$("#rm_group_restore_avatar").toggle(!!group && isDataURL(group.avatar_url));
|
||||
$("#rm_group_chat_name").val(groupName);
|
||||
$("#rm_group_restore_avatar").toggle(!!group && isValidImageUrl(group.avatar_url));
|
||||
$("#rm_group_filter").val("").trigger("input");
|
||||
$(`input[name="rm_group_activation_strategy"][value="${replyStrategy}"]`).prop('checked', true);
|
||||
|
||||
@@ -1108,9 +1145,18 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
$("#rm_group_automode_label").hide();
|
||||
}
|
||||
|
||||
eventSource.emit('groupSelected', {detail: {id: openGroupId, group: group}});
|
||||
eventSource.emit('groupSelected', { detail: { id: openGroupId, group: group } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the upload and processing of a group avatar.
|
||||
* The selected image is read, cropped using a popup, processed into a thumbnail,
|
||||
* and then uploaded to the server.
|
||||
*
|
||||
* @param {Event} event - The event triggered by selecting a file input, containing the image file to upload.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the processing and upload is complete.
|
||||
*/
|
||||
async function uploadGroupAvatar(event) {
|
||||
const file = event.target.files[0];
|
||||
|
||||
@@ -1133,16 +1179,22 @@ async function uploadGroupAvatar(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thumbnail = await createThumbnail(croppedImage, 96, 144);
|
||||
|
||||
let thumbnail = await createThumbnail(croppedImage, 96, 144);
|
||||
//remove data:image/whatever;base64
|
||||
thumbnail = thumbnail.replace(/^data:image\/[a-z]+;base64,/, "");
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
// filename should be group id + human readable timestamp
|
||||
const filename = `${_thisGroup.id}_${humanizedDateTime()}`;
|
||||
let thumbnailUrl = await saveBase64AsFile(thumbnail, openGroupId.toString(), filename, 'jpg');
|
||||
if (!openGroupId) {
|
||||
$('#group_avatar_preview img').attr('src', thumbnail);
|
||||
$('#group_avatar_preview img').attr('src', thumbnailUrl);
|
||||
$('#rm_group_restore_avatar').show();
|
||||
return;
|
||||
}
|
||||
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
_thisGroup.avatar_url = thumbnail;
|
||||
|
||||
|
||||
_thisGroup.avatar_url = thumbnailUrl;
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||
$("#rm_group_restore_avatar").show();
|
||||
await editGroup(openGroupId, true, true);
|
||||
@@ -1223,8 +1275,11 @@ function updateFavButtonState(state) {
|
||||
$("#group_favorite_button").toggleClass('fav_off', !fav_grp_checked);
|
||||
}
|
||||
|
||||
async function selectGroup() {
|
||||
const groupId = $(this).data("id");
|
||||
export async function openGroupById(groupId) {
|
||||
if (!groups.find(x => x.id === groupId)) {
|
||||
console.log('Group not found', groupId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_send_press && !is_group_generating) {
|
||||
if (selected_group !== groupId) {
|
||||
@@ -1265,16 +1320,8 @@ function openCharacterDefinition(characterSelect) {
|
||||
}
|
||||
|
||||
function filterGroupMembers() {
|
||||
const searchValue = $(this).val().trim().toLowerCase();
|
||||
|
||||
if (!searchValue) {
|
||||
$("#rm_group_add_members .group_member").removeClass('hiddenBySearch');
|
||||
} else {
|
||||
$("#rm_group_add_members .group_member").each(function () {
|
||||
const isValidSearch = $(this).find(".ch_name").text().toLowerCase().includes(searchValue);
|
||||
$(this).toggleClass('hiddenBySearch', !isValidSearch);
|
||||
});
|
||||
}
|
||||
const searchValue = $(this).val().toLowerCase();
|
||||
groupCandidatesFilter.setFilterData(FILTER_TYPES.SEARCH, searchValue);
|
||||
}
|
||||
|
||||
async function createGroup() {
|
||||
@@ -1299,7 +1346,7 @@ async function createGroup() {
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
members: members,
|
||||
avatar_url: isDataURL(avatar_url) ? avatar_url : default_avatar,
|
||||
avatar_url: isValidImageUrl(avatar_url) ? avatar_url : default_avatar,
|
||||
allow_self_responses: allow_self_responses,
|
||||
activation_strategy: activation_strategy,
|
||||
disabled_members: [],
|
||||
@@ -1545,7 +1592,10 @@ function doCurMemberListPopout() {
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
$(document).on("click", ".group_select", selectGroup);
|
||||
$(document).on("click", ".group_select", function () {
|
||||
const groupId = $(this).data("id");
|
||||
openGroupById(groupId);
|
||||
});
|
||||
$("#rm_group_filter").on("input", filterGroupMembers);
|
||||
$("#rm_group_submit").on("click", createGroup);
|
||||
$("#rm_group_scenario").on("click", setScenarioOverride);
|
||||
|
@@ -8,6 +8,8 @@ import {
|
||||
import { SECRET_KEYS, writeSecret } from "./secrets.js";
|
||||
import { delay } from "./utils.js";
|
||||
import { deviceInfo } from "./RossAscends-mods.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import { autoSelectInstructPreset } from "./instruct-mode.js";
|
||||
|
||||
export {
|
||||
horde_settings,
|
||||
@@ -226,19 +228,11 @@ async function showKudos() {
|
||||
|
||||
jQuery(function () {
|
||||
$("#horde_model").on('mousedown change', async function (e) {
|
||||
//desktop-only routine for multi-select without CTRL
|
||||
/*if (deviceInfo.device.type === 'desktop') {
|
||||
let hordeModelSelectScrollTop = null;
|
||||
e.preventDefault();
|
||||
const option = $(e.target);
|
||||
const selectElement = $(this)[0];
|
||||
hordeModelSelectScrollTop = selectElement.scrollTop;
|
||||
option.prop('selected', !option.prop('selected'));
|
||||
await delay(1);
|
||||
selectElement.scrollTop = hordeModelSelectScrollTop;
|
||||
}*/
|
||||
horde_settings.models = $('#horde_model').val();
|
||||
console.log('Updated Horde models', horde_settings.models);
|
||||
|
||||
// Try select instruct preset
|
||||
autoSelectInstructPreset(horde_settings.models.join(' '));
|
||||
});
|
||||
|
||||
$("#horde_auto_adjust_response_length").on("input", function () {
|
||||
|
277
public/scripts/instruct-mode.js
Normal file
277
public/scripts/instruct-mode.js
Normal file
@@ -0,0 +1,277 @@
|
||||
"use strict";
|
||||
|
||||
import { saveSettingsDebounced, substituteParams } from "../script.js";
|
||||
import { selected_group } from "./group-chats.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
|
||||
export let instruct_presets = [];
|
||||
|
||||
const controls = [
|
||||
{ id: "instruct_enabled", property: "enabled", isCheckbox: true },
|
||||
{ id: "instruct_wrap", property: "wrap", isCheckbox: true },
|
||||
{ id: "instruct_system_prompt", property: "system_prompt", isCheckbox: false },
|
||||
{ id: "instruct_system_sequence", property: "system_sequence", isCheckbox: false },
|
||||
{ id: "instruct_separator_sequence", property: "separator_sequence", isCheckbox: false },
|
||||
{ id: "instruct_input_sequence", property: "input_sequence", isCheckbox: false },
|
||||
{ id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false },
|
||||
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
|
||||
{ id: "instruct_names", property: "names", isCheckbox: true },
|
||||
{ id: "instruct_macro", property: "macro", isCheckbox: true },
|
||||
{ id: "instruct_names_force_groups", property: "names_force_groups", isCheckbox: true },
|
||||
{ id: "instruct_last_output_sequence", property: "last_output_sequence", isCheckbox: false },
|
||||
{ id: "instruct_activation_regex", property: "activation_regex", isCheckbox: false },
|
||||
];
|
||||
|
||||
/**
|
||||
* Loads instruct mode settings from the given data object.
|
||||
* @param {object} data Settings data object.
|
||||
*/
|
||||
export function loadInstructMode(data) {
|
||||
if (data.instruct !== undefined) {
|
||||
instruct_presets = data.instruct;
|
||||
}
|
||||
|
||||
if (power_user.instruct.names_force_groups === undefined) {
|
||||
power_user.instruct.names_force_groups = true;
|
||||
}
|
||||
|
||||
controls.forEach(control => {
|
||||
const $element = $(`#${control.id}`);
|
||||
|
||||
if (control.isCheckbox) {
|
||||
$element.prop('checked', power_user.instruct[control.property]);
|
||||
} else {
|
||||
$element.val(power_user.instruct[control.property]);
|
||||
}
|
||||
|
||||
$element.on('input', function () {
|
||||
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
});
|
||||
|
||||
instruct_presets.forEach((preset) => {
|
||||
const name = preset.name;
|
||||
const option = document.createElement('option');
|
||||
option.value = name;
|
||||
option.innerText = name;
|
||||
option.selected = name === power_user.instruct.preset;
|
||||
$('#instruct_presets').append(option);
|
||||
});
|
||||
|
||||
highlightDefaultPreset();
|
||||
}
|
||||
|
||||
function highlightDefaultPreset() {
|
||||
$('#instruct_set_default').toggleClass('default', power_user.default_instruct === power_user.instruct.preset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically select instruct preset based on model id.
|
||||
* Otherwise, if default instruct preset is set, selects it.
|
||||
* @param {string} modelId Model name reported by the API.
|
||||
* @returns {boolean} True if instruct preset was activated by model id, false otherwise.
|
||||
*/
|
||||
export function autoSelectInstructPreset(modelId) {
|
||||
// If instruct mode is disabled, don't do anything
|
||||
if (!power_user.instruct.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const preset of instruct_presets) {
|
||||
// If activation regex is set, check if it matches the model id
|
||||
if (preset.activation_regex) {
|
||||
try {
|
||||
const regex = new RegExp(preset.activation_regex, 'i');
|
||||
|
||||
// Stop on first match so it won't cycle back and forth between presets if multiple regexes match
|
||||
if (regex.test(modelId)) {
|
||||
// If preset is not already selected, select it
|
||||
if (power_user.instruct.preset !== preset.name) {
|
||||
$('#instruct_presets').val(preset.name).trigger('change');
|
||||
toastr.info(`Instruct mode: preset "${preset.name}" auto-selected`);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// If regex is invalid, ignore it
|
||||
console.warn(`Invalid instruct activation regex in preset "${preset.name}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (power_user.default_instruct && power_user.instruct.preset !== power_user.default_instruct) {
|
||||
if (instruct_presets.some(p => p.name === power_user.default_instruct)) {
|
||||
console.log(`Instruct mode: default preset "${power_user.default_instruct}" selected`);
|
||||
$('#instruct_presets').val(power_user.default_instruct).trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts instruct mode sequences to an array of stopping strings.
|
||||
* @returns {string[]} Array of instruct mode stopping strings.
|
||||
*/
|
||||
export function getInstructStoppingSequences() {
|
||||
function addInstructSequence(sequence) {
|
||||
// Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string
|
||||
// But it's a problem for Metharme which doesn't use newlines to separate them.
|
||||
const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s;
|
||||
// Sequence must be a non-empty string
|
||||
if (typeof sequence === 'string' && sequence.length > 0) {
|
||||
// If sequence is just a whitespace or newline - we don't want to make it a stopping string
|
||||
// User can always add it as a custom stop string if really needed
|
||||
if (sequence.trim().length > 0) {
|
||||
const wrappedSequence = wrap(sequence);
|
||||
// Need to respect "insert macro" setting
|
||||
const stopString = power_user.instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence;
|
||||
result.push(stopString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = [];
|
||||
|
||||
if (power_user.instruct.enabled) {
|
||||
const input_sequence = power_user.instruct.input_sequence;
|
||||
const output_sequence = power_user.instruct.output_sequence;
|
||||
const last_output_sequence = power_user.instruct.last_output_sequence;
|
||||
|
||||
const combined_sequence = `${input_sequence}\n${output_sequence}\n${last_output_sequence}`;
|
||||
|
||||
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats instruct mode chat message.
|
||||
* @param {string} name Character name.
|
||||
* @param {string} mes Message text.
|
||||
* @param {boolean} isUser Is the message from the user.
|
||||
* @param {boolean} isNarrator Is the message from the narrator.
|
||||
* @param {string} forceAvatar Force avatar string.
|
||||
* @param {string} name1 User name.
|
||||
* @param {string} name2 Character name.
|
||||
* @returns {string} Formatted instruct mode chat message.
|
||||
*/
|
||||
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
|
||||
let includeNames = isNarrator ? false : power_user.instruct.names;
|
||||
|
||||
if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) {
|
||||
includeNames = true;
|
||||
}
|
||||
|
||||
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence && !isUser
|
||||
? power_user.instruct.separator_sequence
|
||||
: separator;
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}` + separatorSequence] : [sequence, mes + separatorSequence];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats example messages according to instruct mode settings.
|
||||
* @param {string} mesExamples Example messages string.
|
||||
* @param {string} name1 User name.
|
||||
* @param {string} name2 Character name.
|
||||
* @returns {string} Formatted example messages string.
|
||||
*/
|
||||
export function formatInstructModeExamples(mesExamples, name1, name2) {
|
||||
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
|
||||
|
||||
let inputSequence = power_user.instruct.input_sequence;
|
||||
let outputSequence = power_user.instruct.output_sequence;
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
inputSequence = substituteParams(inputSequence, name1, name2);
|
||||
outputSequence = substituteParams(outputSequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence ? power_user.instruct.separator_sequence : separator;
|
||||
|
||||
mesExamples = mesExamples.replace(new RegExp(`\n${name1}: `, "gm"), separatorSequence + inputSequence + separator + (includeNames ? `${name1}: ` : ''));
|
||||
mesExamples = mesExamples.replace(new RegExp(`\n${name2}: `, "gm"), separator + outputSequence + separator + (includeNames ? `${name2}: ` : ''));
|
||||
|
||||
return mesExamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats instruct mode last prompt line.
|
||||
* @param {string} name Character name.
|
||||
* @param {boolean} isImpersonate Is generation in impersonation mode.
|
||||
* @param {string} promptBias Prompt bias string.
|
||||
* @param {string} name1 User name.
|
||||
* @param {string} name2 Character name.
|
||||
*/
|
||||
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
|
||||
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
|
||||
const getOutputSequence = () => power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
|
||||
let sequence = isImpersonate ? power_user.instruct.input_sequence : getOutputSequence();
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
|
||||
|
||||
if (!isImpersonate && promptBias) {
|
||||
text += (includeNames ? promptBias : (separator + promptBias));
|
||||
}
|
||||
|
||||
return text.trimEnd() + (includeNames ? '' : separator);
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
$('#instruct_set_default').on('click', function () {
|
||||
if (power_user.instruct.preset === power_user.default_instruct) {
|
||||
power_user.default_instruct = null;
|
||||
$(this).removeClass('default');
|
||||
toastr.info('Default instruct preset cleared');
|
||||
} else {
|
||||
power_user.default_instruct = power_user.instruct.preset;
|
||||
$(this).addClass('default');
|
||||
toastr.info(`Default instruct preset set to ${power_user.default_instruct}`);
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#instruct_presets').on('change', function () {
|
||||
const name = $(this).find(':selected').val();
|
||||
const preset = instruct_presets.find(x => x.name === name);
|
||||
|
||||
if (!preset) {
|
||||
return;
|
||||
}
|
||||
|
||||
power_user.instruct.preset = name;
|
||||
controls.forEach(control => {
|
||||
if (preset[control.property] !== undefined) {
|
||||
power_user.instruct[control.property] = preset[control.property];
|
||||
const $element = $(`#${control.id}`);
|
||||
|
||||
if (control.isCheckbox) {
|
||||
$element.prop('checked', power_user.instruct[control.property]).trigger('input');
|
||||
} else {
|
||||
$element.val(power_user.instruct[control.property]).trigger('input');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
highlightDefaultPreset();
|
||||
});
|
||||
});
|
@@ -576,7 +576,7 @@ function calculateLogitBias() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms instruction into compatible format for Novel AI.
|
||||
* Transforms instruction into compatible format for Novel AI if Novel AI instruct format not already detected.
|
||||
* 1. Instruction must begin and end with curly braces followed and preceded by a space.
|
||||
* 2. Instruction must not contain square brackets as it serves different purpose in NAI.
|
||||
* @param {string} prompt Original instruction prompt
|
||||
@@ -584,7 +584,10 @@ function calculateLogitBias() {
|
||||
*/
|
||||
export function adjustNovelInstructionPrompt(prompt) {
|
||||
const stripedPrompt = prompt.replace(/[\[\]]/g, '').trim();
|
||||
return `{ ${stripedPrompt} }`;
|
||||
if (!stripedPrompt.includes('{ ')) {
|
||||
return `{ ${stripedPrompt} }`;
|
||||
}
|
||||
return stripedPrompt;
|
||||
}
|
||||
|
||||
export async function generateNovelWithStreaming(generate_data, signal) {
|
||||
|
@@ -19,7 +19,6 @@ import {
|
||||
system_message_types,
|
||||
replaceBiasMarkup,
|
||||
is_send_press,
|
||||
saveSettings,
|
||||
Generate,
|
||||
main_api,
|
||||
eventSource,
|
||||
@@ -45,9 +44,10 @@ import {
|
||||
} from "./secrets.js";
|
||||
|
||||
import {
|
||||
deepClone,
|
||||
delay,
|
||||
download,
|
||||
getFileText,
|
||||
getFileText, getSortableDelay,
|
||||
getStringHash,
|
||||
parseJsonFile,
|
||||
stringFormat,
|
||||
@@ -113,9 +113,13 @@ const scale_max = 7900; // Probably more. Save some for the system prompt define
|
||||
const claude_max = 8000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
|
||||
const palm2_max = 7500; // The real context window is 8192, spare some for padding due to using turbo tokenizer
|
||||
const claude_100k_max = 99000;
|
||||
let ai21_max = 9200; //can easily fit 9k gpt tokens because j2's tokenizer is efficient af
|
||||
const unlocked_max = 100 * 1024;
|
||||
const oai_max_temp = 2.0;
|
||||
const claude_max_temp = 1.0;
|
||||
const claude_max_temp = 1.0; //same as j2
|
||||
const j2_max_topk = 10.0;
|
||||
const j2_max_freq = 5.0;
|
||||
const j2_max_pres = 5.0;
|
||||
const openrouter_website_model = 'OR_Website';
|
||||
|
||||
let biasCache = undefined;
|
||||
@@ -161,13 +165,26 @@ export const chat_completion_sources = {
|
||||
CLAUDE: 'claude',
|
||||
SCALE: 'scale',
|
||||
OPENROUTER: 'openrouter',
|
||||
AI21: 'ai21',
|
||||
};
|
||||
|
||||
const prefixMap = selected_group ? {
|
||||
assistant: "",
|
||||
user: "",
|
||||
system: "OOC: "
|
||||
}
|
||||
: {
|
||||
assistant: "{{char}}:",
|
||||
user: "{{user}}:",
|
||||
system: ""
|
||||
};
|
||||
|
||||
const default_settings = {
|
||||
preset_settings_openai: 'Default',
|
||||
temp_openai: 0.9,
|
||||
freq_pen_openai: 0.7,
|
||||
pres_pen_openai: 0.7,
|
||||
count_pen: 0.0,
|
||||
top_p_openai: 1.0,
|
||||
top_k_openai: 0,
|
||||
stream_openai: false,
|
||||
@@ -188,6 +205,7 @@ const default_settings = {
|
||||
wi_format: default_wi_format,
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
claude_model: 'claude-instant-v1',
|
||||
ai21_model: 'j2-ultra',
|
||||
windowai_model: '',
|
||||
openrouter_model: openrouter_website_model,
|
||||
jailbreak_system: false,
|
||||
@@ -199,6 +217,8 @@ const default_settings = {
|
||||
show_external_models: false,
|
||||
proxy_password: '',
|
||||
assistant_prefill: '',
|
||||
use_ai21_tokenizer: false,
|
||||
exclude_assistant: false,
|
||||
};
|
||||
|
||||
const oai_settings = {
|
||||
@@ -206,6 +226,7 @@ const oai_settings = {
|
||||
temp_openai: 1.0,
|
||||
freq_pen_openai: 0,
|
||||
pres_pen_openai: 0,
|
||||
count_pen: 0.0,
|
||||
top_p_openai: 1.0,
|
||||
top_k_openai: 0,
|
||||
stream_openai: false,
|
||||
@@ -226,6 +247,7 @@ const oai_settings = {
|
||||
wi_format: default_wi_format,
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
claude_model: 'claude-instant-v1',
|
||||
ai21_model: 'j2-ultra',
|
||||
windowai_model: '',
|
||||
openrouter_model: openrouter_website_model,
|
||||
jailbreak_system: false,
|
||||
@@ -237,6 +259,8 @@ const oai_settings = {
|
||||
show_external_models: false,
|
||||
proxy_password: '',
|
||||
assistant_prefill: '',
|
||||
use_ai21_tokenizer: false,
|
||||
exclude_assistant: false,
|
||||
};
|
||||
|
||||
let openai_setting_names;
|
||||
@@ -341,17 +365,22 @@ function setupChatCompletionPromptManager(openAiSettings) {
|
||||
containerIdentifier: 'completion_prompt_manager',
|
||||
listIdentifier: 'completion_prompt_manager_list',
|
||||
toggleDisabled: ['main'],
|
||||
draggable: true,
|
||||
sortableDelay: getSortableDelay(),
|
||||
defaultPrompts: {
|
||||
main: default_main_prompt,
|
||||
nsfw: default_nsfw_prompt,
|
||||
jailbreak: default_jailbreak_prompt,
|
||||
enhanceDefinitions: default_enhance_definitions_prompt
|
||||
}
|
||||
},
|
||||
promptOrder: {
|
||||
strategy: 'global',
|
||||
dummyId: 100000
|
||||
},
|
||||
};
|
||||
|
||||
promptManager.saveServiceSettings = () => {
|
||||
return saveSettings();
|
||||
saveSettingsDebounced();
|
||||
return new Promise((resolve) => eventSource.once(event_types.SETTINGS_UPDATED, resolve));
|
||||
}
|
||||
|
||||
promptManager.tryGenerate = () => {
|
||||
@@ -361,7 +390,7 @@ function setupChatCompletionPromptManager(openAiSettings) {
|
||||
promptManager.tokenHandler = tokenHandler;
|
||||
|
||||
promptManager.init(configuration, openAiSettings);
|
||||
promptManager.render();
|
||||
promptManager.render(false);
|
||||
|
||||
return promptManager;
|
||||
}
|
||||
@@ -451,11 +480,19 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt =
|
||||
// Chat History
|
||||
chatCompletion.add(new MessageCollection('chatHistory'), prompts.index('chatHistory'));
|
||||
|
||||
let names = (selected_group && groups.find(x => x.id === selected_group)?.members.map(member => characters.find(c => c.avatar === member)?.name).filter(Boolean).join(', ')) || '';
|
||||
// Reserve budget for new chat message
|
||||
const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt;
|
||||
const newChatMessage = new Message('system', newChat, 'newMainChat');
|
||||
const newChatMessage = new Message('system', substituteParams(newChat, null, null, null, names), 'newMainChat');
|
||||
chatCompletion.reserveBudget(newChatMessage);
|
||||
|
||||
// Reserve budget for group nudge
|
||||
let groupNudgeMessage = null;
|
||||
if (selected_group) {
|
||||
const groupNudgeMessage = Message.fromPrompt(prompts.get('groupNudge'));
|
||||
chatCompletion.reserveBudget(groupNudgeMessage);
|
||||
}
|
||||
|
||||
// Reserve budget for continue nudge
|
||||
let continueMessage = null;
|
||||
if (type === 'continue' && cyclePrompt) {
|
||||
@@ -484,7 +521,8 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt =
|
||||
const chatMessage = Message.fromPrompt(promptManager.preparePrompt(prompt));
|
||||
|
||||
if (true === promptManager.serviceSettings.names_in_completion && prompt.name) {
|
||||
chatMessage.name = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name);
|
||||
const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name);
|
||||
chatMessage.setName(messageName);
|
||||
}
|
||||
|
||||
if (chatCompletion.canAfford(chatMessage)) chatCompletion.insertAtStart(chatMessage, 'chatHistory');
|
||||
@@ -496,6 +534,12 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt =
|
||||
chatCompletion.freeBudget(newChatMessage);
|
||||
chatCompletion.insertAtStart(newChatMessage, 'chatHistory');
|
||||
|
||||
// Reserve budget for group nudge
|
||||
if (selected_group && groupNudgeMessage) {
|
||||
chatCompletion.freeBudget(groupNudgeMessage);
|
||||
chatCompletion.insertAtEnd(groupNudgeMessage, 'chatHistory');
|
||||
}
|
||||
|
||||
// Insert and free continue nudge
|
||||
if (type === 'continue' && continueMessage) {
|
||||
chatCompletion.freeBudget(continueMessage);
|
||||
@@ -513,9 +557,11 @@ function populateDialogueExamples(prompts, chatCompletion) {
|
||||
chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples'));
|
||||
if (openai_msgs_example.length) {
|
||||
const newExampleChat = new Message('system', oai_settings.new_example_chat_prompt, 'newChat');
|
||||
chatCompletion.reserveBudget(newExampleChat);
|
||||
|
||||
[...openai_msgs_example].forEach((dialogue, dialogueIndex) => {
|
||||
let examplesAdded = 0;
|
||||
|
||||
if (chatCompletion.canAfford(newExampleChat)) chatCompletion.insert(newExampleChat, 'dialogueExamples');
|
||||
|
||||
dialogue.forEach((prompt, promptIndex) => {
|
||||
const role = 'system';
|
||||
const content = prompt.content || '';
|
||||
@@ -525,14 +571,14 @@ function populateDialogueExamples(prompts, chatCompletion) {
|
||||
chatMessage.setName(prompt.name);
|
||||
if (chatCompletion.canAfford(chatMessage)) {
|
||||
chatCompletion.insert(chatMessage, 'dialogueExamples');
|
||||
examplesAdded++;
|
||||
}
|
||||
});
|
||||
|
||||
if (0 === examplesAdded) {
|
||||
chatCompletion.removeLastFrom('dialogueExamples');
|
||||
}
|
||||
});
|
||||
|
||||
chatCompletion.freeBudget(newExampleChat);
|
||||
|
||||
const chatExamples = chatCompletion.getMessages().getItemByIdentifier('dialogueExamples').getCollection();
|
||||
if (chatExamples.length) chatCompletion.insertAtStart(newExampleChat, 'dialogueExamples');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,9 +717,10 @@ function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, ty
|
||||
*
|
||||
* @returns {Object} prompts - The prepared and merged system and user-defined prompts.
|
||||
*/
|
||||
function preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts) {
|
||||
function preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride) {
|
||||
const scenarioText = Scenario ? `[Circumstances and context of the dialogue: ${Scenario}]` : '';
|
||||
const charPersonalityText = charPersonality ? `[${name2}'s personality: ${charPersonality}]` : '';
|
||||
const charPersonalityText = charPersonality ? `[${name2}'s personality: ${charPersonality}]` : ''
|
||||
const groupNudge = `[Write the next reply only as ${name2}]`;
|
||||
|
||||
// Create entries for system prompts
|
||||
const systemPrompts = [
|
||||
@@ -687,7 +734,8 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world
|
||||
{ role: 'system', content: oai_settings.nsfw_avoidance_prompt, identifier: 'nsfwAvoidance' },
|
||||
{ role: 'system', content: oai_settings.impersonation_prompt, identifier: 'impersonate' },
|
||||
{ role: 'system', content: quietPrompt, identifier: 'quietPrompt' },
|
||||
{ role: 'system', content: bias, identifier: 'bias' }
|
||||
{ role: 'system', content: bias, identifier: 'bias' },
|
||||
{ role: 'system', content: groupNudge, identifier: 'groupNudge' }
|
||||
];
|
||||
|
||||
// Tavern Extras - Summary
|
||||
@@ -724,7 +772,6 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world
|
||||
});
|
||||
|
||||
// Apply character-specific main prompt
|
||||
const systemPromptOverride = promptManager.activeCharacter.data?.system_prompt ?? null;
|
||||
const systemPrompt = prompts.get('main') ?? null;
|
||||
if (systemPromptOverride && systemPrompt) {
|
||||
const mainOriginalContent = systemPrompt.content;
|
||||
@@ -734,7 +781,6 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world
|
||||
}
|
||||
|
||||
// Apply character-specific jailbreak
|
||||
const jailbreakPromptOverride = promptManager.activeCharacter.data?.post_history_instructions ?? null;
|
||||
const jailbreakPrompt = prompts.get('jailbreak') ?? null;
|
||||
if (jailbreakPromptOverride && jailbreakPrompt) {
|
||||
const jbOriginalContent = jailbreakPrompt.content;
|
||||
@@ -778,7 +824,9 @@ function prepareOpenAIMessages({
|
||||
type,
|
||||
quietPrompt,
|
||||
extensionPrompts,
|
||||
cyclePrompt
|
||||
cyclePrompt,
|
||||
systemPromptOverride,
|
||||
jailbreakPromptOverride,
|
||||
} = {}, dryRun) {
|
||||
// Without a character selected, there is no way to accurately calculate tokens
|
||||
if (!promptManager.activeCharacter && dryRun) return [null, false];
|
||||
@@ -791,7 +839,7 @@ function prepareOpenAIMessages({
|
||||
|
||||
try {
|
||||
// Merge markers and ordered user prompts with system prompts
|
||||
const prompts = preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts);
|
||||
const prompts = preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride);
|
||||
|
||||
// Fill the chat completion with as much context as the budget allows
|
||||
populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, type, cyclePrompt });
|
||||
@@ -806,8 +854,10 @@ function prepareOpenAIMessages({
|
||||
promptManager.error = 'The name of at least one character contained whitespaces or special characters. Please check your user and character name.';
|
||||
} else {
|
||||
toastr.error('An unknown error occurred while counting tokens. Further information may be available in console.')
|
||||
chatCompletion.log('Unexpected error:');
|
||||
chatCompletion.log('----- Unexpected error while preparing prompts -----');
|
||||
chatCompletion.log(error);
|
||||
chatCompletion.log(error.stack);
|
||||
chatCompletion.log('----------------------------------------------------');
|
||||
}
|
||||
} finally {
|
||||
// Pass chat completion to prompt manager for inspection
|
||||
@@ -966,6 +1016,8 @@ function getChatCompletionModel() {
|
||||
return '';
|
||||
case chat_completion_sources.OPENROUTER:
|
||||
return oai_settings.openrouter_model !== openrouter_website_model ? oai_settings.openrouter_model : null;
|
||||
case chat_completion_sources.AI21:
|
||||
return oai_settings.ai21_model;
|
||||
default:
|
||||
throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
|
||||
}
|
||||
@@ -1047,9 +1099,18 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
const isClaude = oai_settings.chat_completion_source == chat_completion_sources.CLAUDE;
|
||||
const isOpenRouter = oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER;
|
||||
const isScale = oai_settings.chat_completion_source == chat_completion_sources.SCALE;
|
||||
const isAI21 = oai_settings.chat_completion_source == chat_completion_sources.AI21;
|
||||
const isTextCompletion = oai_settings.chat_completion_source == chat_completion_sources.OPENAI && (oai_settings.openai_model.startsWith('text-') || oai_settings.openai_model.startsWith('code-'));
|
||||
const stream = type !== 'quiet' && oai_settings.stream_openai && !isScale;
|
||||
const isQuiet = type === 'quiet';
|
||||
const stream = oai_settings.stream_openai && !isQuiet && !isScale && !isAI21;
|
||||
|
||||
if (isAI21) {
|
||||
const joinedMsgs = openai_msgs_tosend.reduce((acc, obj) => {
|
||||
const prefix = prefixMap[obj.role];
|
||||
return acc + (prefix ? (selected_group ? "\n" : prefix + " ") : "") + obj.content + "\n";
|
||||
}, "");
|
||||
openai_msgs_tosend = substituteParams(joinedMsgs);
|
||||
}
|
||||
|
||||
// If we're using the window.ai extension, use that instead
|
||||
// Doesn't support logit bias yet
|
||||
@@ -1089,9 +1150,9 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
if (isClaude) {
|
||||
generate_data['use_claude'] = true;
|
||||
generate_data['top_k'] = parseFloat(oai_settings.top_k_openai);
|
||||
|
||||
generate_data['exclude_assistant'] = oai_settings.exclude_assistant;
|
||||
// Don't add a prefill on quiet gens (summarization)
|
||||
if (!isQuiet) {
|
||||
if (!isQuiet && !oai_settings.exclude_assistant) {
|
||||
generate_data['assistant_prefill'] = substituteParams(oai_settings.assistant_prefill);
|
||||
}
|
||||
}
|
||||
@@ -1106,6 +1167,13 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
generate_data['api_url_scale'] = oai_settings.api_url_scale;
|
||||
}
|
||||
|
||||
if (isAI21) {
|
||||
generate_data['use_ai21'] = true;
|
||||
generate_data['top_k'] = parseFloat(oai_settings.top_k_openai);
|
||||
generate_data['count_pen'] = parseFloat(oai_settings.count_pen);
|
||||
generate_data['stop_tokens'] = [name1 + ':', oai_settings.new_chat_prompt, oai_settings.new_group_chat_prompt];
|
||||
}
|
||||
|
||||
const generate_url = '/generate_openai';
|
||||
const response = await fetch(generate_url, {
|
||||
method: 'POST',
|
||||
@@ -1296,6 +1364,7 @@ class TokenHandler {
|
||||
}
|
||||
|
||||
function countTokens(messages, full = false) {
|
||||
let shouldTokenizeAI21 = oai_settings.chat_completion_source === chat_completion_sources.AI21 && oai_settings.use_ai21_tokenizer;
|
||||
let chatId = 'undefined';
|
||||
|
||||
try {
|
||||
@@ -1321,19 +1390,19 @@ function countTokens(messages, full = false) {
|
||||
|
||||
for (const message of messages) {
|
||||
const model = getTokenizerModel();
|
||||
const hash = getStringHash(message.content);
|
||||
const hash = getStringHash(JSON.stringify(message));
|
||||
const cacheKey = `${model}-${hash}`;
|
||||
const cachedCount = tokenCache[chatId][cacheKey];
|
||||
|
||||
if (typeof cachedCount === 'number') {
|
||||
token_count += cachedCount;
|
||||
}
|
||||
else {
|
||||
|
||||
else {
|
||||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST', //
|
||||
url: `/tokenize_openai?model=${model}`,
|
||||
url: shouldTokenizeAI21 ? '/tokenize_ai21' : `/tokenize_openai?model=${model}`,
|
||||
data: JSON.stringify([message]),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
@@ -1393,8 +1462,8 @@ class Message {
|
||||
this.role = role;
|
||||
this.content = content;
|
||||
|
||||
if (this.content) {
|
||||
this.tokens = tokenHandler.count({ role: this.role, content: this.content })
|
||||
if (typeof this.content === 'string') {
|
||||
this.tokens = tokenHandler.count({ role: this.role, content: this.content });
|
||||
} else {
|
||||
this.tokens = 0;
|
||||
}
|
||||
@@ -1402,6 +1471,7 @@ class Message {
|
||||
|
||||
setName(name) {
|
||||
this.name = name;
|
||||
this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1613,6 +1683,21 @@ class ChatCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the last item of the collection
|
||||
*
|
||||
* @param identifier
|
||||
*/
|
||||
removeLastFrom(identifier) {
|
||||
const index = this.findMessageIndex(identifier);
|
||||
const message = this.messages.collection[index].collection.pop();
|
||||
|
||||
this.increaseTokenBudgetBy(message.getTokens());
|
||||
|
||||
this.log(`Removed ${message.identifier} from ${identifier}. Remaining tokens: ${this.tokenBudget}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the token budget can afford the tokens of the specified message.
|
||||
*
|
||||
@@ -1849,6 +1934,7 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.temp_openai = settings.temp_openai ?? default_settings.temp_openai;
|
||||
oai_settings.freq_pen_openai = settings.freq_pen_openai ?? default_settings.freq_pen_openai;
|
||||
oai_settings.pres_pen_openai = settings.pres_pen_openai ?? default_settings.pres_pen_openai;
|
||||
oai_settings.count_pen = settings.count_pen ?? default_settings.count_pen;
|
||||
oai_settings.top_p_openai = settings.top_p_openai ?? default_settings.top_p_openai;
|
||||
oai_settings.top_k_openai = settings.top_k_openai ?? default_settings.top_k_openai;
|
||||
oai_settings.stream_openai = settings.stream_openai ?? default_settings.stream_openai;
|
||||
@@ -1864,6 +1950,7 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.claude_model = settings.claude_model ?? default_settings.claude_model;
|
||||
oai_settings.windowai_model = settings.windowai_model ?? default_settings.windowai_model;
|
||||
oai_settings.openrouter_model = settings.openrouter_model ?? default_settings.openrouter_model;
|
||||
oai_settings.ai21_model = settings.ai21_model ?? default_settings.ai21_model;
|
||||
oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
|
||||
oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;
|
||||
oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;
|
||||
@@ -1882,7 +1969,8 @@ function loadOpenAISettings(data, settings) {
|
||||
if (settings.wrap_in_quotes !== undefined) oai_settings.wrap_in_quotes = !!settings.wrap_in_quotes;
|
||||
if (settings.names_in_completion !== undefined) oai_settings.names_in_completion = !!settings.names_in_completion;
|
||||
if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model;
|
||||
|
||||
if (settings.use_ai21_tokenizer !== undefined) { oai_settings.use_ai21_tokenizer = !!settings.use_ai21_tokenizer; oai_settings.use_ai21_tokenizer ? ai21_max = 8191 : ai21_max = 9200; }
|
||||
if (settings.exclude_assistant !== undefined) oai_settings.exclude_assistant = !!settings.exclude_assistant;
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
$('#api_url_scale').val(oai_settings.api_url_scale);
|
||||
$('#openai_proxy_password').val(oai_settings.proxy_password);
|
||||
@@ -1894,6 +1982,8 @@ function loadOpenAISettings(data, settings) {
|
||||
$(`#model_claude_select option[value="${oai_settings.claude_model}"`).attr('selected', true);
|
||||
$('#model_windowai_select').val(oai_settings.windowai_model);
|
||||
$(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).attr('selected', true);
|
||||
$('#model_ai21_select').val(oai_settings.ai21_model);
|
||||
$(`#model_ai21_select option[value="${oai_settings.ai21_model}"`).attr('selected', true);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context);
|
||||
$('#openai_max_context_counter').text(`${oai_settings.openai_max_context}`);
|
||||
$('#model_openrouter_select').val(oai_settings.openrouter_model);
|
||||
@@ -1909,7 +1999,8 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#legacy_streaming').prop('checked', oai_settings.legacy_streaming);
|
||||
$('#openai_show_external_models').prop('checked', oai_settings.show_external_models);
|
||||
$('#openai_external_category').toggle(oai_settings.show_external_models);
|
||||
|
||||
$('#use_ai21_tokenizer').prop('checked', oai_settings.use_ai21_tokenizer);
|
||||
$('#exclude_assistant').prop('checked', oai_settings.exclude_assistant);
|
||||
if (settings.impersonation_prompt !== undefined) oai_settings.impersonation_prompt = settings.impersonation_prompt;
|
||||
|
||||
$('#impersonation_prompt_textarea').val(oai_settings.impersonation_prompt);
|
||||
@@ -1932,6 +2023,9 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#pres_pen_openai').val(oai_settings.pres_pen_openai);
|
||||
$('#pres_pen_counter_openai').text(Number(oai_settings.pres_pen_openai).toFixed(2));
|
||||
|
||||
$('#count_pen').val(oai_settings.count_pen);
|
||||
$('#count_pen_counter').text(Number(oai_settings.count_pen).toFixed(2));
|
||||
|
||||
$('#top_p_openai').val(oai_settings.top_p_openai);
|
||||
$('#top_p_counter_openai').text(Number(oai_settings.top_p_openai).toFixed(2));
|
||||
|
||||
@@ -1974,7 +2068,7 @@ async function getStatusOpen() {
|
||||
return resultCheckStatusOpen();
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE || oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE || oai_settings.chat_completion_source == chat_completion_sources.CLAUDE || oai_settings.chat_completion_source == chat_completion_sources.AI21) {
|
||||
let status = 'Unable to verify key; press "Test Message" to validate.';
|
||||
setOnlineStatus(status);
|
||||
return resultCheckStatusOpen();
|
||||
@@ -2071,9 +2165,11 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
claude_model: settings.claude_model,
|
||||
windowai_model: settings.windowai_model,
|
||||
openrouter_model: settings.openrouter_model,
|
||||
ai21_model: settings.ai21_model,
|
||||
temperature: settings.temp_openai,
|
||||
frequency_penalty: settings.freq_pen_openai,
|
||||
presence_penalty: settings.pres_pen_openai,
|
||||
count_penalty: settings.count_pen,
|
||||
top_p: settings.top_p_openai,
|
||||
top_k: settings.top_k_openai,
|
||||
openai_max_context: settings.openai_max_context,
|
||||
@@ -2101,6 +2197,8 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
api_url_scale: settings.api_url_scale,
|
||||
show_external_models: settings.show_external_models,
|
||||
assistant_prefill: settings.assistant_prefill,
|
||||
use_ai21_tokenizer: settings.use_ai21_tokenizer,
|
||||
exclude_assistant: settings.exclude_assistant,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
@@ -2286,7 +2384,11 @@ async function onExportPresetClick() {
|
||||
return;
|
||||
}
|
||||
|
||||
const preset = openai_settings[openai_setting_names[oai_settings.preset_settings_openai]];
|
||||
const preset = deepClone(openai_settings[openai_setting_names[oai_settings.preset_settings_openai]]);
|
||||
|
||||
delete preset.reverse_proxy;
|
||||
delete preset.proxy_password;
|
||||
|
||||
const presetJsonString = JSON.stringify(preset, null, 4);
|
||||
download(presetJsonString, oai_settings.preset_settings_openai, 'application/json');
|
||||
}
|
||||
@@ -2400,6 +2502,7 @@ function onSettingsPresetChange() {
|
||||
temperature: ['#temp_openai', 'temp_openai', false],
|
||||
frequency_penalty: ['#freq_pen_openai', 'freq_pen_openai', false],
|
||||
presence_penalty: ['#pres_pen_openai', 'pres_pen_openai', false],
|
||||
count_penalty: ['#count_pen', 'count_pen', false],
|
||||
top_p: ['#top_p_openai', 'top_p_openai', false],
|
||||
top_k: ['#top_k_openai', 'top_k_openai', false],
|
||||
max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true],
|
||||
@@ -2407,6 +2510,7 @@ function onSettingsPresetChange() {
|
||||
claude_model: ['#model_claude_select', 'claude_model', false],
|
||||
windowai_model: ['#model_windowai_select', 'windowai_model', false],
|
||||
openrouter_model: ['#model_openrouter_select', 'openrouter_model', false],
|
||||
ai21_model: ['#model_ai21_select', 'ai21_model', false],
|
||||
openai_max_context: ['#openai_max_context', 'openai_max_context', false],
|
||||
openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false],
|
||||
wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true],
|
||||
@@ -2430,6 +2534,8 @@ function onSettingsPresetChange() {
|
||||
show_external_models: ['#openai_show_external_models', 'show_external_models', true],
|
||||
proxy_password: ['#openai_proxy_password', 'proxy_password', false],
|
||||
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
|
||||
use_ai21_tokenizer: ['#use_ai21_tokenizer', 'use_ai21_tokenizer', false],
|
||||
exclude_assistant: ['#exclude_assistant', 'exclude_assistant', false],
|
||||
};
|
||||
|
||||
const presetName = $('#settings_perset_openai').find(":selected").text();
|
||||
@@ -2554,6 +2660,11 @@ async function onModelChange() {
|
||||
oai_settings.openrouter_model = value;
|
||||
}
|
||||
|
||||
if ($(this).is('#model_ai21_select')) {
|
||||
console.log('AI21 model changed to', value);
|
||||
oai_settings.ai21_model = value;
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
@@ -2640,6 +2751,38 @@ async function onModelChange() {
|
||||
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.AI21) {
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
} else {
|
||||
$('#openai_max_context').attr('max', ai21_max);
|
||||
}
|
||||
|
||||
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
|
||||
oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
|
||||
$('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
|
||||
oai_settings.freq_pen_openai = Math.min(j2_max_freq, oai_settings.freq_pen_openai < 0 ? 0 : oai_settings.freq_pen_openai);
|
||||
$('#freq_pen_openai').attr('min', 0).attr('max', j2_max_freq).val(oai_settings.freq_pen_openai).trigger('input');
|
||||
|
||||
oai_settings.pres_pen_openai = Math.min(j2_max_pres, oai_settings.pres_pen_openai < 0 ? 0 : oai_settings.pres_pen_openai);
|
||||
$('#pres_pen_openai').attr('min', 0).attr('max', j2_max_pres).val(oai_settings.pres_pen_openai).trigger('input');
|
||||
|
||||
oai_settings.top_k_openai = Math.min(j2_max_topk, oai_settings.top_k_openai);
|
||||
$('#top_k_openai').attr('max', j2_max_topk).val(oai_settings.top_k_openai).trigger('input');
|
||||
} else if (oai_settings.chat_completion_source != chat_completion_sources.AI21) {
|
||||
oai_settings.freq_pen_openai = Math.min(2.0, oai_settings.freq_pen_openai);
|
||||
$('#freq_pen_openai').attr('min', -2.0).attr('max', 2.0).val(oai_settings.freq_pen_openai).trigger('input');
|
||||
|
||||
oai_settings.freq_pen_openai = Math.min(2.0, oai_settings.pres_pen_openai);
|
||||
$('#pres_pen_openai').attr('min', -2.0).attr('max', 2.0).val(oai_settings.freq_pen_openai).trigger('input');
|
||||
|
||||
oai_settings.top_k_openai = Math.min(200, oai_settings.top_k_openai);
|
||||
$('#top_k_openai').attr('max', 200).val(oai_settings.top_k_openai).trigger('input');
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value);
|
||||
}
|
||||
@@ -2730,6 +2873,19 @@ async function onConnectButtonClick(e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.AI21) {
|
||||
const api_key_ai21 = $('#api_key_ai21').val().trim();
|
||||
|
||||
if (api_key_ai21.length) {
|
||||
await writeSecret(SECRET_KEYS.AI21, api_key_ai21);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.AI21]) {
|
||||
console.log('No secret key saved for AI21');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$("#api_loading_openai").css("display", 'inline-block');
|
||||
$("#api_button_openai").css("display", 'none');
|
||||
saveSettingsDebounced();
|
||||
@@ -2759,11 +2915,17 @@ function toggleChatCompletionForms() {
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {
|
||||
$('#model_openrouter_select').trigger('change');
|
||||
}
|
||||
|
||||
else if (oai_settings.chat_completion_source == chat_completion_sources.AI21) {
|
||||
$('#model_ai21_select').trigger('change');
|
||||
}
|
||||
$('[data-source]').each(function () {
|
||||
const validSources = $(this).data('source').split(',');
|
||||
$(this).toggle(validSources.includes(oai_settings.chat_completion_source));
|
||||
});
|
||||
|
||||
if (chat_completion_sources.CLAUDE == oai_settings.chat_completion_source) {
|
||||
$('#claude_assistant_prefill_block').toggle(!oai_settings.exclude_assistant);
|
||||
}
|
||||
}
|
||||
|
||||
async function testApiConnection() {
|
||||
@@ -2817,7 +2979,12 @@ $(document).ready(async function () {
|
||||
oai_settings.pres_pen_openai = $(this).val();
|
||||
$('#pres_pen_counter_openai').text(Number($(this).val()).toFixed(2));
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).on('input', '#count_pen', function () {
|
||||
oai_settings.count_pen = $(this).val();
|
||||
$('#count_pen_counter').text(Number($(this).val()).toFixed(2));
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).on('input', '#top_p_openai', function () {
|
||||
@@ -2855,6 +3022,20 @@ $(document).ready(async function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#use_ai21_tokenizer').on('change', function () {
|
||||
oai_settings.use_ai21_tokenizer = !!$('#use_ai21_tokenizer').prop('checked');
|
||||
oai_settings.use_ai21_tokenizer ? ai21_max = 8191 : ai21_max = 9200;
|
||||
oai_settings.openai_max_context = Math.min(ai21_max, oai_settings.openai_max_context);
|
||||
$('#openai_max_context').attr('max', ai21_max).val(oai_settings.openai_max_context).trigger('input');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#exclude_assistant').on('change', function () {
|
||||
oai_settings.exclude_assistant = !!$('#exclude_assistant').prop('checked');
|
||||
$('#claude_assistant_prefill_block').toggle(!oai_settings.exclude_assistant);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#names_in_completion').on('change', function () {
|
||||
oai_settings.names_in_completion = !!$('#names_in_completion').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
@@ -3022,6 +3203,7 @@ $(document).ready(async function () {
|
||||
$("#model_windowai_select").on("change", onModelChange);
|
||||
$("#model_scale_select").on("change", onModelChange);
|
||||
$("#model_openrouter_select").on("change", onModelChange);
|
||||
$("#model_ai21_select").on("change", onModelChange);
|
||||
$("#settings_perset_openai").on("change", onSettingsPresetChange);
|
||||
$("#new_oai_preset").on("click", onNewPresetClick);
|
||||
$("#delete_oai_preset").on("click", onDeletePresetClick);
|
||||
|
@@ -12,8 +12,6 @@ import {
|
||||
event_types,
|
||||
getCurrentChatId,
|
||||
printCharacters,
|
||||
name1,
|
||||
name2,
|
||||
setCharacterId,
|
||||
setEditedMessageId
|
||||
} from "../script.js";
|
||||
@@ -21,8 +19,8 @@ import { isMobile, initMovingUI } from "./RossAscends-mods.js";
|
||||
import {
|
||||
groups,
|
||||
resetSelectedGroup,
|
||||
selected_group,
|
||||
} from "./group-chats.js";
|
||||
import { loadInstructMode } from "./instruct-mode.js";
|
||||
|
||||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
|
||||
@@ -44,9 +42,7 @@ export {
|
||||
export const MAX_CONTEXT_DEFAULT = 4096;
|
||||
const MAX_CONTEXT_UNLOCKED = 65536;
|
||||
|
||||
const defaultStoryString = `{{#if description}}{{description}}{{/if}}
|
||||
{{#if personality}}{{personality}}{{/if}}
|
||||
{{#if scenario}}Scenario: {{scenario}}{{/if}}`;
|
||||
const defaultStoryString = "{{#if system}}{{system}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}";
|
||||
const defaultExampleSeparator = '***';
|
||||
const defaultChatStart = '***';
|
||||
|
||||
@@ -97,6 +93,7 @@ let power_user = {
|
||||
collapse_newlines: false,
|
||||
pygmalion_formatting: pygmalion_options.AUTO,
|
||||
pin_examples: false,
|
||||
strip_examples: false,
|
||||
trim_sentences: false,
|
||||
include_newline: false,
|
||||
always_force_name2: false,
|
||||
@@ -165,6 +162,7 @@ let power_user = {
|
||||
trim_spaces: true,
|
||||
relaxed_api_urls: false,
|
||||
|
||||
default_instruct: '',
|
||||
instruct: {
|
||||
enabled: false,
|
||||
wrap: true,
|
||||
@@ -179,6 +177,7 @@ let power_user = {
|
||||
separator_sequence: '',
|
||||
macro: false,
|
||||
names_force_groups: true,
|
||||
activation_regex: '',
|
||||
},
|
||||
|
||||
context: {
|
||||
@@ -205,7 +204,6 @@ let power_user = {
|
||||
|
||||
let themes = [];
|
||||
let movingUIPresets = [];
|
||||
let instruct_presets = [];
|
||||
let context_presets = [];
|
||||
|
||||
const storage_keys = {
|
||||
@@ -665,9 +663,6 @@ function loadPowerUserSettings(settings, data) {
|
||||
movingUIPresets = data.movingUIPresets;
|
||||
}
|
||||
|
||||
if (data.instruct !== undefined) {
|
||||
instruct_presets = data.instruct;
|
||||
}
|
||||
|
||||
if (data.context !== undefined) {
|
||||
context_presets = data.context;
|
||||
@@ -731,6 +726,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
$("#spoiler_free_mode").prop("checked", power_user.spoiler_free_mode);
|
||||
$("#collapse-newlines-checkbox").prop("checked", power_user.collapse_newlines);
|
||||
$("#pin-examples-checkbox").prop("checked", power_user.pin_examples);
|
||||
$("#remove-examples-checkbox").prop("checked", power_user.strip_examples);
|
||||
$("#always-force-name2-checkbox").prop("checked", power_user.always_force_name2);
|
||||
$("#trim_sentences_checkbox").prop("checked", power_user.trim_sentences);
|
||||
$("#include_newline_checkbox").prop("checked", power_user.include_newline);
|
||||
@@ -802,7 +798,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
|
||||
$(`#character_sort_order option[data-order="${power_user.sort_order}"][data-field="${power_user.sort_field}"]`).prop("selected", true);
|
||||
reloadMarkdownProcessor(power_user.render_formulas);
|
||||
loadInstructMode();
|
||||
loadInstructMode(data);
|
||||
loadContextSettings();
|
||||
loadMaxContextUnlocked();
|
||||
switchWaifuMode();
|
||||
@@ -812,7 +808,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
}
|
||||
|
||||
async function loadCharListState() {
|
||||
if (document.getElementById('CharID0') !== null) {
|
||||
if (document.querySelector('.character_select') !== null) {
|
||||
console.debug('setting charlist state to...')
|
||||
if (power_user.charListGrid === true) {
|
||||
console.debug('..to grid')
|
||||
@@ -926,74 +922,6 @@ function loadContextSettings() {
|
||||
});
|
||||
}
|
||||
|
||||
function loadInstructMode() {
|
||||
const controls = [
|
||||
{ id: "instruct_enabled", property: "enabled", isCheckbox: true },
|
||||
{ id: "instruct_wrap", property: "wrap", isCheckbox: true },
|
||||
{ id: "instruct_system_prompt", property: "system_prompt", isCheckbox: false },
|
||||
{ id: "instruct_system_sequence", property: "system_sequence", isCheckbox: false },
|
||||
{ id: "instruct_separator_sequence", property: "separator_sequence", isCheckbox: false },
|
||||
{ id: "instruct_input_sequence", property: "input_sequence", isCheckbox: false },
|
||||
{ id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false },
|
||||
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
|
||||
{ id: "instruct_names", property: "names", isCheckbox: true },
|
||||
{ id: "instruct_macro", property: "macro", isCheckbox: true },
|
||||
{ id: "instruct_names_force_groups", property: "names_force_groups", isCheckbox: true },
|
||||
{ id: "instruct_last_output_sequence", property: "last_output_sequence", isCheckbox: false },
|
||||
];
|
||||
|
||||
if (power_user.instruct.names_force_groups === undefined) {
|
||||
power_user.instruct.names_force_groups = true;
|
||||
}
|
||||
|
||||
controls.forEach(control => {
|
||||
const $element = $(`#${control.id}`);
|
||||
|
||||
if (control.isCheckbox) {
|
||||
$element.prop('checked', power_user.instruct[control.property]);
|
||||
} else {
|
||||
$element.val(power_user.instruct[control.property]);
|
||||
}
|
||||
|
||||
$element.on('input', function () {
|
||||
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
});
|
||||
|
||||
instruct_presets.forEach((preset) => {
|
||||
const name = preset.name;
|
||||
const option = document.createElement('option');
|
||||
option.value = name;
|
||||
option.innerText = name;
|
||||
option.selected = name === power_user.instruct.preset;
|
||||
$('#instruct_presets').append(option);
|
||||
});
|
||||
|
||||
$('#instruct_presets').on('change', function () {
|
||||
const name = $(this).find(':selected').val();
|
||||
const preset = instruct_presets.find(x => x.name === name);
|
||||
|
||||
if (!preset) {
|
||||
return;
|
||||
}
|
||||
|
||||
power_user.instruct.preset = name;
|
||||
controls.forEach(control => {
|
||||
if (preset[control.property] !== undefined) {
|
||||
power_user.instruct[control.property] = preset[control.property];
|
||||
const $element = $(`#${control.id}`);
|
||||
|
||||
if (control.isCheckbox) {
|
||||
$element.prop('checked', power_user.instruct[control.property]).trigger('input');
|
||||
} else {
|
||||
$element.val(power_user.instruct[control.property]).trigger('input');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function fuzzySearchCharacters(searchValue) {
|
||||
const fuse = new Fuse(characters, {
|
||||
keys: [
|
||||
@@ -1050,58 +978,6 @@ export function renderStoryString(params) {
|
||||
}
|
||||
}
|
||||
|
||||
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
|
||||
let includeNames = isNarrator ? false : power_user.instruct.names;
|
||||
|
||||
if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) {
|
||||
includeNames = true;
|
||||
}
|
||||
|
||||
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence && !isUser
|
||||
? power_user.instruct.separator_sequence
|
||||
: (power_user.instruct.wrap ? '\n' : '');
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}`, separatorSequence] : [sequence, mes, separatorSequence];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
|
||||
export function formatInstructStoryString(story, systemPrompt) {
|
||||
// If the character has a custom system prompt AND user has it preferred, use that instead of the default
|
||||
systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : power_user.instruct.system_prompt;
|
||||
const sequence = power_user.instruct.system_sequence || '';
|
||||
const prompt = substituteParams(systemPrompt, name1, name2, power_user.instruct.system_prompt) || '';
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const textArray = [sequence, prompt + '\n' + story];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
|
||||
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
|
||||
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
|
||||
const getOutputSequence = () => power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
|
||||
let sequence = isImpersonate ? power_user.instruct.input_sequence : getOutputSequence();
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
|
||||
|
||||
if (!isImpersonate && promptBias) {
|
||||
text += (includeNames ? promptBias : (separator + promptBias));
|
||||
}
|
||||
|
||||
return text.trimEnd();
|
||||
}
|
||||
|
||||
const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);
|
||||
const compareFunc = (first, second) => {
|
||||
if (power_user.sort_order == 'random') {
|
||||
@@ -1677,10 +1553,27 @@ $(document).ready(() => {
|
||||
});
|
||||
|
||||
$("#pin-examples-checkbox").change(function () {
|
||||
if ($(this).prop("checked")) {
|
||||
$("#remove-examples-checkbox").prop("checked", false).prop("disabled", true);
|
||||
power_user.strip_examples = false;
|
||||
} else {
|
||||
$("#remove-examples-checkbox").prop("disabled", false);
|
||||
}
|
||||
power_user.pin_examples = !!$(this).prop("checked");
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#remove-examples-checkbox").change(function () {
|
||||
if ($(this).prop("checked")) {
|
||||
$("#pin-examples-checkbox").prop("checked", false).prop("disabled", true);
|
||||
power_user.pin_examples = false;
|
||||
} else {
|
||||
$("#pin-examples-checkbox").prop("disabled", false);
|
||||
}
|
||||
power_user.strip_examples = !!$(this).prop("checked");
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
// include newline is the child of trim sentences
|
||||
// if include newline is checked, trim sentences must be checked
|
||||
// if trim sentences is unchecked, include newline must be unchecked
|
||||
|
@@ -16,13 +16,15 @@ import {
|
||||
this_chid,
|
||||
} from "../script.js";
|
||||
import { groups, selected_group } from "./group-chats.js";
|
||||
import { instruct_presets } from "./instruct-mode.js";
|
||||
import { kai_settings } from "./kai-settings.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import {
|
||||
textgenerationwebui_preset_names,
|
||||
textgenerationwebui_presets,
|
||||
textgenerationwebui_settings,
|
||||
} from "./textgen-settings.js";
|
||||
import { download, parseJsonFile, waitUntilCondition } from "./utils.js";
|
||||
import { deepClone, download, parseJsonFile, waitUntilCondition } from "./utils.js";
|
||||
|
||||
const presetManagers = {};
|
||||
|
||||
@@ -55,8 +57,10 @@ function autoSelectPreset() {
|
||||
}
|
||||
}
|
||||
|
||||
function getPresetManager() {
|
||||
const apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
|
||||
function getPresetManager(apiId) {
|
||||
if (!apiId) {
|
||||
apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
|
||||
}
|
||||
|
||||
if (!Object.keys(presetManagers).includes(apiId)) {
|
||||
return null;
|
||||
@@ -162,6 +166,10 @@ class PresetManager {
|
||||
presets = textgenerationwebui_presets;
|
||||
preset_names = textgenerationwebui_preset_names;
|
||||
break;
|
||||
case "instruct":
|
||||
presets = instruct_presets;
|
||||
preset_names = instruct_presets.map(x => x.name);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown API ID ${this.apiId}`);
|
||||
}
|
||||
@@ -169,12 +177,20 @@ class PresetManager {
|
||||
return { presets, preset_names };
|
||||
}
|
||||
|
||||
isKeyedApi() {
|
||||
return this.apiId == "textgenerationwebui" || this.apiId == "instruct";
|
||||
}
|
||||
|
||||
isNonGenericApi() {
|
||||
return this.apiId == "instruct";
|
||||
}
|
||||
|
||||
updateList(name, preset) {
|
||||
const { presets, preset_names } = this.getPresetList();
|
||||
const presetExists = this.apiId == "textgenerationwebui" ? preset_names.includes(name) : Object.keys(preset_names).includes(name);
|
||||
const presetExists = this.isKeyedApi() ? preset_names.includes(name) : Object.keys(preset_names).includes(name);
|
||||
|
||||
if (presetExists) {
|
||||
if (this.apiId == "textgenerationwebui") {
|
||||
if (this.isKeyedApi()) {
|
||||
presets[preset_names.indexOf(name)] = preset;
|
||||
$(this.select).find(`option[value="${name}"]`).prop('selected', true);
|
||||
$(this.select).val(name).trigger("change");
|
||||
@@ -189,8 +205,8 @@ class PresetManager {
|
||||
else {
|
||||
presets.push(preset);
|
||||
const value = presets.length - 1;
|
||||
// ooba is reversed
|
||||
if (this.apiId == "textgenerationwebui") {
|
||||
|
||||
if (this.isKeyedApi()) {
|
||||
preset_names[value] = name;
|
||||
const option = $('<option></option>', { value: name, text: name, selected: true });
|
||||
$(this.select).append(option);
|
||||
@@ -214,6 +230,10 @@ class PresetManager {
|
||||
return nai_settings;
|
||||
case "textgenerationwebui":
|
||||
return textgenerationwebui_settings;
|
||||
case "instruct":
|
||||
const preset = deepClone(power_user.instruct);
|
||||
preset['name'] = power_user.instruct.preset;
|
||||
return preset;
|
||||
default:
|
||||
console.warn(`Unknown API ID ${apiId}`);
|
||||
return {};
|
||||
@@ -229,6 +249,7 @@ class PresetManager {
|
||||
'streaming_novel',
|
||||
'nai_preamble',
|
||||
'model_novel',
|
||||
"enabled",
|
||||
];
|
||||
const settings = Object.assign({}, getSettingsByApiId(this.apiId));
|
||||
|
||||
@@ -238,8 +259,10 @@ class PresetManager {
|
||||
}
|
||||
}
|
||||
|
||||
settings['genamt'] = amount_gen;
|
||||
settings['max_length'] = max_context;
|
||||
if (!this.isNonGenericApi()) {
|
||||
settings['genamt'] = amount_gen;
|
||||
settings['max_length'] = max_context;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
@@ -256,7 +279,7 @@ class PresetManager {
|
||||
|
||||
$(this.select).find(`option[value="${value}"]`).remove();
|
||||
|
||||
if (this.apiId == "textgenerationwebui") {
|
||||
if (this.isKeyedApi()) {
|
||||
preset_names.splice(preset_names.indexOf(value), 1);
|
||||
} else {
|
||||
delete preset_names[nameToDelete];
|
||||
@@ -289,9 +312,11 @@ jQuery(async () => {
|
||||
eventSource.on(event_types.CHAT_CHANGED, autoSelectPreset);
|
||||
registerPresetManagers();
|
||||
$(document).on("click", "[data-preset-manager-update]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
const apiId = $(this).data("preset-manager-update");
|
||||
const presetManager = getPresetManager(apiId);
|
||||
|
||||
if (!presetManager) {
|
||||
console.warn(`Preset Manager not found for API: ${apiId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -299,9 +324,11 @@ jQuery(async () => {
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-new]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
const apiId = $(this).data("preset-manager-new");
|
||||
const presetManager = getPresetManager(apiId);
|
||||
|
||||
if (!presetManager) {
|
||||
console.warn(`Preset Manager not found for API: ${apiId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -309,9 +336,11 @@ jQuery(async () => {
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-export]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
const apiId = $(this).data("preset-manager-export");
|
||||
const presetManager = getPresetManager(apiId);
|
||||
|
||||
if (!presetManager) {
|
||||
console.warn(`Preset Manager not found for API: ${apiId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -323,13 +352,16 @@ jQuery(async () => {
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-import]", async function () {
|
||||
$('[data-preset-manager-file]').trigger('click');
|
||||
const apiId = $(this).data("preset-manager-import");
|
||||
$(`[data-preset-manager-file="${apiId}"]`).trigger('click');
|
||||
});
|
||||
|
||||
$(document).on("change", "[data-preset-manager-file]", async function (e) {
|
||||
const presetManager = getPresetManager();
|
||||
const apiId = $(this).data("preset-manager-file");
|
||||
const presetManager = getPresetManager(apiId);
|
||||
|
||||
if (!presetManager) {
|
||||
console.warn(`Preset Manager not found for API: ${apiId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -348,9 +380,11 @@ jQuery(async () => {
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-delete]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
const apiId = $(this).data("preset-manager-delete");
|
||||
const presetManager = getPresetManager(apiId);
|
||||
|
||||
if (!presetManager) {
|
||||
console.warn(`Preset Manager not found for API: ${apiId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -8,6 +8,7 @@ export const SECRET_KEYS = {
|
||||
CLAUDE: 'api_key_claude',
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
SCALE: 'api_key_scale',
|
||||
AI21: 'api_key_ai21',
|
||||
}
|
||||
|
||||
const INPUT_MAP = {
|
||||
@@ -18,6 +19,7 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.CLAUDE]: '#api_key_claude',
|
||||
[SECRET_KEYS.OPENROUTER]: '#api_key_openrouter',
|
||||
[SECRET_KEYS.SCALE]: '#api_key_scale',
|
||||
[SECRET_KEYS.AI21]: '#api_key_ai21',
|
||||
}
|
||||
|
||||
async function clearSecret() {
|
||||
|
@@ -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) {
|
||||
|
@@ -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();
|
||||
|
@@ -149,9 +149,6 @@ function setWorldInfoSettings(settings, data) {
|
||||
});
|
||||
|
||||
$("#world_editor_select").trigger("change");
|
||||
|
||||
// Update settings
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
// World Info Editor
|
||||
@@ -467,7 +464,7 @@ function appendWorldEntry(name, data, entry) {
|
||||
|
||||
const contentInput = template.find('textarea[name="content"]');
|
||||
contentInput.data("uid", entry.uid);
|
||||
contentInput.on("input", function () {
|
||||
contentInput.on("input", function (_, { skipCount } = {}) {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
data.entries[uid].content = value;
|
||||
@@ -475,12 +472,25 @@ function appendWorldEntry(name, data, entry) {
|
||||
setOriginalDataValue(data, uid, "content", data.entries[uid].content);
|
||||
saveWorldInfo(name, data);
|
||||
|
||||
if (skipCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
// count tokens
|
||||
countTokensDebounced(this, value);
|
||||
});
|
||||
contentInput.val(entry.content).trigger("input");
|
||||
contentInput.val(entry.content).trigger("input", { skipCount: true });
|
||||
//initScrollHeight(contentInput);
|
||||
|
||||
template.find('.inline-drawer-toggle').on('click', function () {
|
||||
const counter = template.find(".world_entry_form_token_counter");
|
||||
|
||||
if (counter.data('first-run')) {
|
||||
counter.data('first-run', false);
|
||||
countTokensDebounced(contentInput, contentInput.val());
|
||||
}
|
||||
});
|
||||
|
||||
// selective
|
||||
const selectiveInput = template.find('input[name="selective"]');
|
||||
selectiveInput.data("uid", entry.uid);
|
||||
|
2289
public/style.css
2289
public/style.css
File diff suppressed because it is too large
Load Diff
293
server.js
293
server.js
@@ -288,6 +288,7 @@ function humanizedISO8601DateTime() {
|
||||
var is_colab = process.env.colaburl !== undefined;
|
||||
var charactersPath = 'public/characters/';
|
||||
var chatsPath = 'public/chats/';
|
||||
const UPLOADS_PATH = './uploads';
|
||||
const AVATAR_WIDTH = 400;
|
||||
const AVATAR_HEIGHT = 600;
|
||||
const jsonParser = express.json({ limit: '100mb' });
|
||||
@@ -296,6 +297,8 @@ const baseRequestArgs = { headers: { "Content-Type": "application/json" } };
|
||||
const directories = {
|
||||
worlds: 'public/worlds/',
|
||||
avatars: 'public/User Avatars',
|
||||
images: 'public/img/',
|
||||
userImages: 'public/user/images/',
|
||||
groups: 'public/groups/',
|
||||
groupChats: 'public/group chats',
|
||||
chats: 'public/chats/',
|
||||
@@ -427,7 +430,7 @@ app.use('/characters', (req, res) => {
|
||||
res.send(data);
|
||||
});
|
||||
});
|
||||
app.use(multer({ dest: "uploads", limits: { fieldSize: 10 * 1024 * 1024 } }).single("avatar"));
|
||||
app.use(multer({ dest: UPLOADS_PATH, limits: { fieldSize: 10 * 1024 * 1024 } }).single("avatar"));
|
||||
app.get("/", function (request, response) {
|
||||
response.sendFile(process.cwd() + "/public/index.html");
|
||||
});
|
||||
@@ -598,7 +601,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
||||
});
|
||||
|
||||
async function* readWebsocket() {
|
||||
const streamingUrl = request.header('X-Streaming-URL');
|
||||
const streamingUrl = request.header('X-Streaming-URL').replace("localhost", "127.0.0.1");
|
||||
const websocket = new WebSocket(streamingUrl);
|
||||
|
||||
websocket.on('open', async function () {
|
||||
@@ -632,7 +635,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
||||
resolve(data, isBinary);
|
||||
});
|
||||
});
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
console.error("Socket error:", err);
|
||||
websocket.close();
|
||||
yield "[SillyTavern] Streaming failed:\n" + err;
|
||||
@@ -1005,7 +1008,7 @@ function charaFormatData(data) {
|
||||
return char;
|
||||
}
|
||||
|
||||
app.post("/createcharacter", urlencodedParser, function (request, response) {
|
||||
app.post("/createcharacter", urlencodedParser, async function (request, response) {
|
||||
if (!request.body) return response.sendStatus(400);
|
||||
|
||||
request.body.ch_name = sanitize(request.body.ch_name);
|
||||
@@ -1022,8 +1025,9 @@ app.post("/createcharacter", urlencodedParser, function (request, response) {
|
||||
charaWrite(defaultAvatar, char, internalName, response, avatarName);
|
||||
} else {
|
||||
const crop = tryParse(request.query.crop);
|
||||
const uploadPath = path.join("./uploads/", request.file.filename);
|
||||
charaWrite(uploadPath, char, internalName, response, avatarName, crop);
|
||||
const uploadPath = path.join(UPLOADS_PATH, request.file.filename);
|
||||
await charaWrite(uploadPath, char, internalName, response, avatarName, crop);
|
||||
fs.unlinkSync(uploadPath);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1120,9 +1124,10 @@ app.post("/editcharacter", urlencodedParser, async function (request, response)
|
||||
await charaWrite(avatarPath, char, target_img, response, 'Character saved');
|
||||
} else {
|
||||
const crop = tryParse(request.query.crop);
|
||||
const newAvatarPath = path.join("./uploads/", request.file.filename);
|
||||
const newAvatarPath = path.join(UPLOADS_PATH, request.file.filename);
|
||||
invalidateThumbnail('avatar', request.body.avatar_url);
|
||||
await charaWrite(newAvatarPath, char, target_img, response, 'Character saved', crop);
|
||||
fs.unlinkSync(newAvatarPath);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
@@ -1373,7 +1378,20 @@ app.post("/getcharacters", jsonParser, function (request, response) {
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/getonecharacter", jsonParser, async function (request, response) {
|
||||
if (!request.body) return response.sendStatus(400);
|
||||
const item = request.body.avatar_url;
|
||||
const filePath = path.join(charactersPath, item);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
characters = {};
|
||||
await processCharacter(item, 0);
|
||||
|
||||
return response.send(characters[0]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle a POST request to get the stats object
|
||||
@@ -1531,13 +1549,14 @@ app.post("/downloadbackground", urlencodedParser, function (request, response) {
|
||||
response_dw_bg = response;
|
||||
if (!request.body || !request.file) return response.sendStatus(400);
|
||||
|
||||
const img_path = path.join("uploads/", request.file.filename);
|
||||
const img_path = path.join(UPLOADS_PATH, request.file.filename);
|
||||
const filename = request.file.originalname;
|
||||
|
||||
try {
|
||||
fs.copyFileSync(img_path, path.join('public/backgrounds/', filename));
|
||||
invalidateThumbnail('bg', filename);
|
||||
response_dw_bg.send(filename);
|
||||
fs.unlinkSync(img_path);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
response_dw_bg.sendStatus(500);
|
||||
@@ -2052,13 +2071,15 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
|
||||
let png_name = '';
|
||||
let filedata = request.file;
|
||||
let uploadPath = path.join('./uploads', filedata.filename);
|
||||
let uploadPath = path.join(UPLOADS_PATH, filedata.filename);
|
||||
var format = request.body.file_type;
|
||||
const defaultAvatarPath = './public/img/ai4.png';
|
||||
//console.log(format);
|
||||
if (filedata) {
|
||||
if (format == 'json') {
|
||||
fs.readFile(uploadPath, 'utf8', async (err, data) => {
|
||||
fs.unlinkSync(uploadPath);
|
||||
|
||||
if (err) {
|
||||
console.log(err);
|
||||
response.send({ error: true });
|
||||
@@ -2139,8 +2160,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
|
||||
if (format == 'webp') {
|
||||
try {
|
||||
let convertedPath = path.join('./uploads', path.basename(uploadPath, ".webp") + ".png")
|
||||
let convertedPath = path.join(UPLOADS_PATH, path.basename(uploadPath, ".webp") + ".png")
|
||||
await webp.dwebp(uploadPath, convertedPath, "-o");
|
||||
fs.unlinkSync(uploadPath);
|
||||
uploadPath = convertedPath;
|
||||
}
|
||||
catch {
|
||||
@@ -2155,7 +2177,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
unsetFavFlag(jsonData);
|
||||
jsonData = readFromV2(jsonData);
|
||||
let char = JSON.stringify(jsonData);
|
||||
charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||
await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||
fs.unlinkSync(uploadPath);
|
||||
} else if (jsonData.name !== undefined) {
|
||||
console.log('Found a v1 character file.');
|
||||
|
||||
@@ -2181,6 +2204,7 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
char = convertToV2(char);
|
||||
char = JSON.stringify(char);
|
||||
await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||
fs.unlinkSync(uploadPath);
|
||||
} else {
|
||||
console.log('Unknown character card format');
|
||||
response.send({ error: true });
|
||||
@@ -2323,9 +2347,9 @@ app.post("/exportcharacter", jsonParser, async function (request, response) {
|
||||
try {
|
||||
let json = await charaRead(filename);
|
||||
let stringByteArray = utf8Encode.encode(json).toString();
|
||||
let inputWebpPath = `./uploads/${Date.now()}_input.webp`;
|
||||
let outputWebpPath = `./uploads/${Date.now()}_output.webp`;
|
||||
let metadataPath = `./uploads/${Date.now()}_metadata.exif`;
|
||||
let inputWebpPath = path.join(UPLOADS_PATH, `${Date.now()}_input.webp`);
|
||||
let outputWebpPath = path.join(UPLOADS_PATH, `${Date.now()}_output.webp`);
|
||||
let metadataPath = path.join(UPLOADS_PATH, `${Date.now()}_metadata.exif`);
|
||||
let metadata =
|
||||
{
|
||||
"Exif": {
|
||||
@@ -2360,7 +2384,10 @@ app.post("/importgroupchat", urlencodedParser, function (request, response) {
|
||||
try {
|
||||
const filedata = request.file;
|
||||
const chatname = humanizedISO8601DateTime();
|
||||
fs.copyFileSync(`./uploads/${filedata.filename}`, (`${directories.groupChats}/${chatname}.jsonl`));
|
||||
const pathToUpload = path.join(UPLOADS_PATH, filedata.filename);
|
||||
const pathToNewFile = path.join(directories.groupChats, `${chatname}.jsonl`);
|
||||
fs.copyFileSync(pathToUpload, pathToNewFile);
|
||||
fs.unlinkSync(pathToUpload);
|
||||
return response.send({ res: chatname });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -2379,7 +2406,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
|
||||
if (filedata) {
|
||||
if (format === 'json') {
|
||||
fs.readFile(`./uploads/${filedata.filename}`, 'utf8', (err, data) => {
|
||||
fs.readFile(path.join(UPLOADS_PATH, filedata.filename), 'utf8', (err, data) => {
|
||||
|
||||
if (err) {
|
||||
console.log(err);
|
||||
@@ -2473,7 +2500,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
}
|
||||
if (format === 'jsonl') {
|
||||
//console.log(humanizedISO8601DateTime()+':imported chat format is JSONL');
|
||||
const fileStream = fs.createReadStream('./uploads/' + filedata.filename);
|
||||
const fileStream = fs.createReadStream(path.join(UPLOADS_PATH, filedata.filename));
|
||||
const rl = readline.createInterface({
|
||||
input: fileStream,
|
||||
crlfDelay: Infinity
|
||||
@@ -2483,7 +2510,7 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
let jsonData = json5.parse(line);
|
||||
|
||||
if (jsonData.user_name !== undefined || jsonData.name !== undefined) {
|
||||
fs.copyFile(`./uploads/${filedata.filename}`, (`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()}.jsonl`), (err) => {
|
||||
fs.copyFile(path.join(UPLOADS_PATH, filedata.filename), (`${chatsPath + avatar_url}/${ch_name} - ${humanizedISO8601DateTime()}.jsonl`), (err) => {
|
||||
if (err) {
|
||||
response.send({ error: true });
|
||||
return console.log(err);
|
||||
@@ -2512,8 +2539,9 @@ app.post('/importworldinfo', urlencodedParser, (request, response) => {
|
||||
if (request.body.convertedData) {
|
||||
fileContents = request.body.convertedData;
|
||||
} else {
|
||||
const pathToUpload = path.join('./uploads/', request.file.filename);
|
||||
const pathToUpload = path.join(UPLOADS_PATH, request.file.filename);
|
||||
fileContents = fs.readFileSync(pathToUpload, 'utf8');
|
||||
fs.unlinkSync(pathToUpload);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -2565,7 +2593,7 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
|
||||
if (!request.file) return response.sendStatus(400);
|
||||
|
||||
try {
|
||||
const pathToUpload = path.join('./uploads/' + request.file.filename);
|
||||
const pathToUpload = path.join(UPLOADS_PATH, request.file.filename);
|
||||
const crop = tryParse(request.query.crop);
|
||||
let rawImg = await jimp.read(pathToUpload);
|
||||
|
||||
@@ -2585,6 +2613,73 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Ensure the directory for the provided file path exists.
|
||||
* If not, it will recursively create the directory.
|
||||
*
|
||||
* @param {string} filePath - The full path of the file for which the directory should be ensured.
|
||||
*/
|
||||
function ensureDirectoryExistence(filePath) {
|
||||
const dirname = path.dirname(filePath);
|
||||
if (fs.existsSync(dirname)) {
|
||||
return true;
|
||||
}
|
||||
ensureDirectoryExistence(dirname);
|
||||
fs.mkdirSync(dirname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint to handle image uploads.
|
||||
* The image should be provided in the request body in base64 format.
|
||||
* Optionally, a character name can be provided to save the image in a sub-folder.
|
||||
*
|
||||
* @route POST /uploadimage
|
||||
* @param {Object} request.body - The request payload.
|
||||
* @param {string} request.body.image - The base64 encoded image data.
|
||||
* @param {string} [request.body.ch_name] - Optional character name to determine the sub-directory.
|
||||
* @returns {Object} response - The response object containing the path where the image was saved.
|
||||
*/
|
||||
app.post('/uploadimage', jsonParser, async (request, response) => {
|
||||
// Check for image data
|
||||
if (!request.body || !request.body.image) {
|
||||
return response.status(400).send({ error: "No image data provided" });
|
||||
}
|
||||
|
||||
// Extracting the base64 data and the image format
|
||||
const match = request.body.image.match(/^data:image\/(png|jpg|webp);base64,(.+)$/);
|
||||
if (!match) {
|
||||
return response.status(400).send({ error: "Invalid image format" });
|
||||
}
|
||||
|
||||
const [, format, base64Data] = match;
|
||||
|
||||
// Constructing filename and path
|
||||
let filename = `${Date.now()}.${format}`;
|
||||
if (request.body.filename) {
|
||||
filename = `${request.body.filename}.${format}`;
|
||||
}
|
||||
|
||||
// if character is defined, save to a sub folder for that character
|
||||
let pathToNewFile = path.join(directories.userImages, filename);
|
||||
if (request.body.ch_name) {
|
||||
pathToNewFile = path.join(directories.userImages, request.body.ch_name, filename);
|
||||
}
|
||||
|
||||
try {
|
||||
ensureDirectoryExistence(pathToNewFile);
|
||||
const imageBuffer = Buffer.from(base64Data, 'base64');
|
||||
await fs.promises.writeFile(pathToNewFile, imageBuffer);
|
||||
// send the path to the image, relative to the client folder, which means removing the first folder from the path which is 'public'
|
||||
pathToNewFile = pathToNewFile.split(path.sep).slice(1).join(path.sep);
|
||||
response.send({ path: pathToNewFile });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response.status(500).send({ error: "Failed to save the image" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.post('/getgroups', jsonParser, (_, response) => {
|
||||
const groups = [];
|
||||
|
||||
@@ -2633,7 +2728,7 @@ app.post('/creategroup', jsonParser, (request, response) => {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const id = Date.now();
|
||||
const id = String(Date.now());
|
||||
const groupMetadata = {
|
||||
id: id,
|
||||
name: request.body.name ?? 'New Group',
|
||||
@@ -2855,6 +2950,26 @@ function invalidateThumbnail(type, file) {
|
||||
}
|
||||
}
|
||||
|
||||
function cleanUploads() {
|
||||
try {
|
||||
if (fs.existsSync(UPLOADS_PATH)) {
|
||||
const uploads = fs.readdirSync(UPLOADS_PATH);
|
||||
|
||||
if (!uploads.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug(`Cleaning uploads folder (${uploads.length} files)`);
|
||||
uploads.forEach(file => {
|
||||
const pathToFile = path.join(UPLOADS_PATH, file);
|
||||
fs.unlinkSync(pathToFile);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureThumbnailCache() {
|
||||
const cacheFiles = fs.readdirSync(directories.thumbnailsBg);
|
||||
|
||||
@@ -3202,9 +3317,9 @@ async function sendClaudeRequest(request, response) {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
let requestPrompt = convertClaudePrompt(request.body.messages, true, true);
|
||||
let requestPrompt = convertClaudePrompt(request.body.messages, true, !request.body.exclude_assistant);
|
||||
|
||||
if (request.body.assistant_prefill) {
|
||||
if (request.body.assistant_prefill && !request.body.exclude_assistant) {
|
||||
requestPrompt += request.body.assistant_prefill;
|
||||
}
|
||||
|
||||
@@ -3277,6 +3392,10 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
return sendScaleRequest(request, response_generate_openai);
|
||||
}
|
||||
|
||||
if (request.body.use_ai21) {
|
||||
return sendAI21Request(request, response_generate_openai);
|
||||
}
|
||||
|
||||
let api_url;
|
||||
let api_key_openai;
|
||||
let headers;
|
||||
@@ -3341,7 +3460,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
config.responseType = 'stream';
|
||||
}
|
||||
|
||||
async function makeRequest(config, response_generate_openai, request, retries = 5, timeout = 1000) {
|
||||
async function makeRequest(config, response_generate_openai, request, retries = 5, timeout = 5000) {
|
||||
try {
|
||||
const response = await axios(config);
|
||||
|
||||
@@ -3363,7 +3482,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 429 && retries > 0) {
|
||||
console.log('Out of quota, retrying...');
|
||||
console.log(`Out of quota, retrying in ${Math.round(timeout / 1000)}s`);
|
||||
setTimeout(() => {
|
||||
makeRequest(config, response_generate_openai, request, retries - 1);
|
||||
}, timeout);
|
||||
@@ -3466,20 +3585,108 @@ app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_op
|
||||
response_tokenize_openai.send({ "token_count": num_tokens });
|
||||
});
|
||||
|
||||
async function sendAI21Request(request, response) {
|
||||
if (!request.body) return response.sendStatus(400);
|
||||
const controller = new AbortController();
|
||||
console.log(request.body.messages)
|
||||
request.socket.removeAllListeners('close');
|
||||
request.socket.on('close', function () {
|
||||
controller.abort();
|
||||
});
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
Authorization: `Bearer ${readSecret(SECRET_KEYS.AI21)}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
numResults: 1,
|
||||
maxTokens: request.body.max_tokens,
|
||||
minTokens: 0,
|
||||
temperature: request.body.temperature,
|
||||
topP: request.body.top_p,
|
||||
stopSequences: request.body.stop_tokens,
|
||||
topKReturn: request.body.top_k,
|
||||
frequencyPenalty: {
|
||||
scale: request.body.frequency_penalty * 100,
|
||||
applyToWhitespaces: false,
|
||||
applyToPunctuations: false,
|
||||
applyToNumbers: false,
|
||||
applyToStopwords: false,
|
||||
applyToEmojis: false
|
||||
},
|
||||
presencePenalty: {
|
||||
scale: request.body.presence_penalty,
|
||||
applyToWhitespaces: false,
|
||||
applyToPunctuations: false,
|
||||
applyToNumbers: false,
|
||||
applyToStopwords: false,
|
||||
applyToEmojis: false
|
||||
},
|
||||
countPenalty: {
|
||||
scale: request.body.count_pen,
|
||||
applyToWhitespaces: false,
|
||||
applyToPunctuations: false,
|
||||
applyToNumbers: false,
|
||||
applyToStopwords: false,
|
||||
applyToEmojis: false
|
||||
},
|
||||
prompt: request.body.messages
|
||||
}),
|
||||
signal: controller.signal,
|
||||
};
|
||||
|
||||
fetch(`https://api.ai21.com/studio/v1/${request.body.model}/complete`, options)
|
||||
.then(r => r.json())
|
||||
.then(r => {
|
||||
if (r.completions === undefined) {
|
||||
console.log(r)
|
||||
} else {
|
||||
console.log(r.completions[0].data.text)
|
||||
}
|
||||
const reply = { choices: [{ "message": { "content": r.completions[0].data.text, } }] };
|
||||
return response.send(reply)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
return response.send({ error: true })
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
app.post("/tokenize_ai21", jsonParser, function (request, response_tokenize_ai21 = response) {
|
||||
if (!request.body) return response_tokenize_ai21.sendStatus(400);
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
Authorization: `Bearer ${readSecret(SECRET_KEYS.AI21)}`
|
||||
},
|
||||
body: JSON.stringify({ text: request.body[0].content })
|
||||
};
|
||||
|
||||
fetch('https://api.ai21.com/studio/v1/tokenize', options)
|
||||
.then(response => response.json())
|
||||
.then(response => response_tokenize_ai21.send({ "token_count": response.tokens.length }))
|
||||
.catch(err => console.error(err));
|
||||
});
|
||||
|
||||
app.post("/save_preset", jsonParser, function (request, response) {
|
||||
const name = sanitize(request.body.name);
|
||||
if (!request.body.preset || !name) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const filename = `${name}.settings`;
|
||||
const directory = getPresetFolderByApiId(request.body.apiId);
|
||||
const settings = getPresetSettingsByAPI(request.body.apiId);
|
||||
const filename = name + settings.extension;
|
||||
|
||||
if (!directory) {
|
||||
if (!settings.folder) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const fullpath = path.join(directory, filename);
|
||||
const fullpath = path.join(settings.folder, filename);
|
||||
writeFileAtomicSync(fullpath, JSON.stringify(request.body.preset, null, 4), 'utf-8');
|
||||
return response.send({ name });
|
||||
});
|
||||
@@ -3490,16 +3697,16 @@ app.post("/delete_preset", jsonParser, function (request, response) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const filename = `${name}.settings`;
|
||||
const directory = getPresetFolderByApiId(request.body.apiId);
|
||||
const settings = getPresetSettingsByAPI(request.body.apiId);
|
||||
const filename = name + settings.extension;
|
||||
|
||||
if (!directory) {
|
||||
if (!settings.folder) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const fullpath = path.join(directory, filename);
|
||||
const fullpath = path.join(settings.folder, filename);
|
||||
|
||||
if (fs.existsSync) {
|
||||
if (fs.existsSync(fullpath)) {
|
||||
fs.unlinkSync(fullpath);
|
||||
return response.sendStatus(200);
|
||||
} else {
|
||||
@@ -3519,17 +3726,19 @@ app.post("/savepreset_openai", jsonParser, function (request, response) {
|
||||
return response.send({ name });
|
||||
});
|
||||
|
||||
function getPresetFolderByApiId(apiId) {
|
||||
function getPresetSettingsByAPI(apiId) {
|
||||
switch (apiId) {
|
||||
case 'kobold':
|
||||
case 'koboldhorde':
|
||||
return directories.koboldAI_Settings;
|
||||
return { folder: directories.koboldAI_Settings, extension: '.settings' };
|
||||
case 'novel':
|
||||
return directories.novelAI_Settings;
|
||||
return { folder: directories.novelAI_Settings, extension: '.settings' };
|
||||
case 'textgenerationwebui':
|
||||
return directories.textGen_Settings;
|
||||
return { folder: directories.textGen_Settings, extension: '.settings' };
|
||||
case 'instruct':
|
||||
return { folder: directories.instruct, extension: '.json' };
|
||||
default:
|
||||
return null;
|
||||
return { folder: null, extension: null };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3623,6 +3832,7 @@ const setupTasks = async function () {
|
||||
ensurePublicDirectoriesExist();
|
||||
await ensureThumbnailCache();
|
||||
contentManager.checkForNewContent();
|
||||
cleanUploads();
|
||||
|
||||
// Colab users could run the embedded tool
|
||||
if (!is_colab) await convertWebp();
|
||||
@@ -3778,6 +3988,7 @@ const SECRET_KEYS = {
|
||||
DEEPL: 'deepl',
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
SCALE: 'api_key_scale',
|
||||
AI21: 'api_key_ai21'
|
||||
}
|
||||
|
||||
function migrateSecrets() {
|
||||
@@ -4175,7 +4386,7 @@ app.post('/upload_sprite_pack', urlencodedParser, async (request, response) => {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
const spritePackPath = path.join("./uploads/", file.filename);
|
||||
const spritePackPath = path.join(UPLOADS_PATH, file.filename);
|
||||
const sprites = await getImageBuffers(spritePackPath);
|
||||
const files = fs.readdirSync(spritesPath);
|
||||
|
||||
@@ -4233,7 +4444,7 @@ app.post('/upload_sprite', urlencodedParser, async (request, response) => {
|
||||
}
|
||||
|
||||
const filename = label + path.parse(file.originalname).ext;
|
||||
const spritePath = path.join("./uploads/", file.filename);
|
||||
const spritePath = path.join(UPLOADS_PATH, file.filename);
|
||||
const pathToFile = path.join(spritesPath, filename);
|
||||
// Copy uploaded file to sprites folder
|
||||
fs.cpSync(spritePath, pathToFile);
|
||||
|
Reference in New Issue
Block a user