Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging

This commit is contained in:
Cohee1207 2023-07-25 00:01:07 +03:00
commit e15c739e64
28 changed files with 1035 additions and 270 deletions

View File

@ -1,9 +1,9 @@
name: Build and Publish Release (Dev)
name: Build and Publish Release (Release)
on:
push:
branches:
- dev
- release
jobs:
build_and_publish:
@ -30,8 +30,8 @@ jobs:
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ci-dev
name: Continuous Release (Dev)
tag_name: ci-release
name: Continuous Release (Release)
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,9 +1,9 @@
name: Build and Publish Release (Main)
name: Build and Publish Release (Staging)
on:
push:
branches:
- main
- staging
jobs:
build_and_publish:
@ -30,8 +30,8 @@ jobs:
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ci-main
name: Continuous Release (Main)
tag_name: ci-staging
name: Continuous Release (Staging)
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

4
.gitignore vendored
View File

@ -9,12 +9,14 @@ public/worlds/
public/css/bg_load.css
public/themes/
public/OpenAI Settings/
public/KoboldAI Settings/
public/TextGen Settings/
public/scripts/extensions/third-party/
public/stats.json
/uploads/
*.jsonl
/config.conf
/docker/config.conf
/docker/config
.DS_Store
public/settings.json
/thumbnails

View File

@ -4,7 +4,7 @@ FROM node:19.1.0-alpine3.16
ARG APP_HOME=/home/node/app
# Install system dependencies
RUN apk add gcompat tini
RUN apk add gcompat tini git
# Ensure proper handling of kernel signals
ENTRYPOINT [ "tini", "--" ]
@ -23,13 +23,17 @@ COPY . ./
# Copy default chats, characters and user avatars to <folder>.default folder
RUN \
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds,settings.json" && \
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds" && \
\
echo "*** Store default $RESOURCES in <folder>.default ***" && \
for R in $RESOURCES; do mv "public/$R" "public/$R.default"; done && \
\
echo "*** Create symbolic links to config directory ***" && \
for R in $RESOURCES; do ln -s "../config/$R" "public/$R"; done && \
# rm "config.conf" "public/settings.json" "public/css/bg_load.css" && \
ln -s "./config/config.conf" "config.conf" && \
ln -s "../config/settings.json" "public/settings.json" && \
ln -s "../../config/bg_load.css" "public/css/bg_load.css" && \
mkdir "config"
# Cleanup unnecessary files

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 338 KiB

View File

@ -9,5 +9,4 @@ services:
- "8000:8000"
volumes:
- "./config:/home/node/app/config"
- "./config.conf:/home/node/app/config.conf"
restart: unless-stopped

View File

@ -1,7 +1,7 @@
#!/bin/sh
# Initialize missing user files
IFS="," RESOURCES="characters,groups,group chats,chats,User Avatars,worlds,settings.json"
IFS="," RESOURCES="characters,groups,group chats,chats,User Avatars,worlds"
for R in $RESOURCES; do
if [ ! -e "config/$R" ]; then
echo "Resource not found, copying from defaults: $R"
@ -9,5 +9,20 @@ for R in $RESOURCES; do
fi
done
if [ ! -e "config/config.conf" ]; then
echo "Resource not found, copying from defaults: config.conf"
cp -r "default/config.conf" "config/config.conf"
fi
if [ ! -e "config/settings.json" ]; then
echo "Resource not found, copying from defaults: settings.json"
cp -r "default/settings.json" "config/settings.json"
fi
if [ ! -e "config/bg_load.css" ]; then
echo "Resource not found, copying from defaults: bg_load.css"
cp -r "default/bg_load.css" "config/bg_load.css"
fi
# Start the server
exec node server.js

View File

@ -1,6 +1,6 @@
{
"max_length": 100,
"genamt": 100,
"max_length": 2048,
"genamt": 200,
"temp": 1,
"top_k": 0,
"top_p": 0.95,
@ -19,4 +19,4 @@
4,
5
]
}
}

View File

@ -14,7 +14,10 @@
"repetition_penalty_range": 8192,
"repetition_penalty_frequency": 0.03,
"repetition_penalty_presence": 0.005,
"repetition_penalty_slope": 0,
"top_a": 0.075,
"top_k": 79,
"top_p": 0.95
"top_p": 0.95,
"typical_p": 1,
"max_context": 8192
}

View File

@ -5,6 +5,8 @@
"min_length": 1,
"top_k": 25,
"top_p": 1,
"top_a": 0,
"typical_p": 1,
"tail_free_sampling": 0.925,
"repetition_penalty": 1.9,
"repetition_penalty_range": 768,
@ -15,4 +17,4 @@
"return_full_text": false,
"prefix": "vanilla",
"max_context": 8192
}
}

View File

@ -4,6 +4,8 @@
"max_length": 40,
"min_length": 1,
"top_a": 0.022,
"top_k": 0,
"top_p": 1,
"typical_p": 0.9,
"tail_free_sampling": 0.956,
"repetition_penalty": 1.25,
@ -15,4 +17,4 @@
"return_full_text": false,
"prefix": "vanilla",
"max_context": 8192
}
}

View File

@ -5,6 +5,7 @@
"min_length": 1,
"top_k": 25,
"top_a": 0.3,
"top_p": 1,
"typical_p": 0.96,
"tail_free_sampling": 0.895,
"repetition_penalty": 1.0125,
@ -16,4 +17,4 @@
"return_full_text": false,
"prefix": "vanilla",
"max_context": 8192
}
}

View File

@ -6,6 +6,7 @@
"top_k": 79,
"top_p": 0.95,
"top_a": 0.075,
"typical_p": 1,
"tail_free_sampling": 0.989,
"repetition_penalty": 1.5,
"repetition_penalty_range": 8192,
@ -16,4 +17,4 @@
"return_full_text": false,
"prefix": "vanilla",
"max_context": 8192
}
}

View File

@ -5,6 +5,7 @@
"min_length": 1,
"top_k": 0,
"top_p": 0.912,
"top_a": 1,
"typical_p": 0.912,
"tail_free_sampling": 0.921,
"repetition_penalty": 1.21,
@ -16,4 +17,4 @@
"return_full_text": false,
"prefix": "vanilla",
"max_context": 8192
}
}

View File

@ -95,6 +95,7 @@
<script type="module" src="scripts/context-template.js"></script>
<script type="module" src="scripts/extensions.js"></script>
<script type="module" src="scripts/authors-note.js"></script>
<script type="module" src="scripts/preset-manager.js"></script>
<script type="text/javascript" src="scripts/toolcool-color-picker.js"></script>
<title>SillyTavern</title>
@ -131,15 +132,24 @@
<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">
<span class="note-link-span">?</span>
</a>
</h3>
<select id="settings_perset">
<option value="gui" data-i18n="guikoboldaisettings">GUI KoboldAI Settings</option>
</select>
<div class="preset_buttons">
<select id="settings_perset" data-preset-manager-for="kobold">
<option value="gui" data-i18n="guikoboldaisettings">GUI KoboldAI Settings</option>
</select>
<i data-preset-manager-update="kobold" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
<i data-preset-manager-new="kobold" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
<i data-preset-manager-import="kobold" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
<i data-preset-manager-export="kobold" class="menu_button fa-solid fa-download"title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-delete="kobold" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
</div>
</div>
<div id="novel_api-presets">
<h3>
@ -148,7 +158,7 @@
<span class="note-link-span">?</span>
</a>
</h3>
<select id="settings_perset_novel">
<select id="settings_perset_novel" data-preset-manager-for="novel">
<option value="gui" data-i18n="default">Default</option>
</select>
</div>
@ -171,8 +181,15 @@
<div id="textgenerationwebui_api-presets">
<h3><span data-i18n="text gen webio(ooba)preset">Text Gen WebUI (ooba) presets</span>
</h3>
<select id="settings_preset_textgenerationwebui">
</select>
<div class="preset_buttons">
<select id="settings_preset_textgenerationwebui" data-preset-manager-for="textgenerationwebui">
</select>
<i data-preset-manager-update="textgenerationwebui" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
<i data-preset-manager-new="textgenerationwebui" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
<i data-preset-manager-import="textgenerationwebui" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
<i data-preset-manager-export="textgenerationwebui" class="menu_button fa-solid fa-download"title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-delete="textgenerationwebui" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
</div>
</div>
<hr>
</div>
@ -777,6 +794,81 @@
</div>
</div>
<div id="novel_api-settings">
<div class="range-block">
<div class="range-block-title" data-i18n="Top P">
Top P
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="top_p_novel" name="volume" min="0" max="1" step="0.01">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="top_p_novel" id="top_p_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Top A">
Top A
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="top_a_novel" name="volume" min="0" max="1" step="0.01">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="top_a_novel" id="top_a_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Top K">
Top K
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="top_k_novel" name="volume" min="0" max="300" step="1">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="top_k_novel" id="top_k_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Typical P">
Typical P
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="typical_p_novel" name="volume" min="0" max="1" step="0.01">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="typical_p_novel" id="typical_p_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Min Length">
Min Length
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="min_length_novel" name="volume" min="0" max="1024" step="1">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="min_length_novel" id="min_length_counter_novel">
select
</div>
</div>
</div>
</div>
</div>
<div id="textgenerationwebui_api-settings">
<div class="range-block">
@ -2340,7 +2432,7 @@
<span data-i18n="Confirm message deletion">Confirm message deletion</span>
</label>
<label for="spoiler_free_mode"><input id="spoiler_free_mode" type="checkbox" />
<span data-i18n="Spoiler Free Mode">Spolier Free Mode</span>
<span data-i18n="Spoiler Free Mode">Spoiler Free Mode</span>
</label>
<div class="inline-drawer wide100p flexFlowColumn">

View File

@ -1,6 +1,6 @@
{
"name": "Llama 2",
"system_prompt": "Write {{user}}'s next reply in this fictional roleplay with {{char}}.\n<</SYS>>\n",
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.\n<</SYS>>\n",
"system_sequence": "[INST] <<SYS>>\n",
"stop_sequence": "",
"input_sequence": "[INST]",

View File

@ -76,6 +76,7 @@ import {
persona_description_positions,
loadMovingUIState,
getCustomStoppingStrings,
MAX_CONTEXT_DEFAULT,
} from "./scripts/power-user.js";
import {
@ -701,11 +702,11 @@ var is_use_scroll_holder = false;
//settings
var settings;
var koboldai_settings;
var koboldai_setting_names;
export let koboldai_settings;
export let koboldai_setting_names;
var preset_settings = "gui";
var user_avatar = "you.png";
var amount_gen = 80; //default max length of AI generated responses
export var amount_gen = 80; //default max length of AI generated responses
var max_context = 2048;
var is_pygmalion = false;
@ -719,8 +720,8 @@ let extension_prompts = {};
var main_api;// = "kobold";
//novel settings
let novel_tier;
let novelai_settings;
let novelai_setting_names;
export let novelai_settings;
export let novelai_setting_names;
let abortController;
//css
@ -2110,7 +2111,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
// OpenAI doesn't need instruct mode. Use OAI main prompt instead.
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
const isImpersonate = type == "impersonate";
const isContinue = type == 'continue';
message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
// Name for the multigen prefix
@ -2199,6 +2199,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
type = 'continue';
}
const isContinue = type == 'continue';
deactivateSendButtons();
let { messageBias, promptBias, isUserPromptBias } = getBiasStrings(textareaText, type);
@ -3151,6 +3152,17 @@ async function DupeChar() {
return;
}
const confirm = await callPopup(`
<h3>Are you sure you want to duplicate this character?</h3>
<span>If you just want to start a new chat with the same character, use "Start new chat" option in the bottom-left options menu.</span><br><br>`,
'confirm',
);
if (!confirm) {
console.log('User cancelled duplication');
return;
}
const body = { avatar_url: characters[this_chid].avatar };
const response = await fetch('/dupecharacter', {
method: 'POST',
@ -4907,11 +4919,6 @@ async function getSettings(type) {
novelai_setting_names = {};
novelai_setting_names = arr_holder;
nai_settings.preset_settings_novel = settings.preset_settings_novel;
$(
`#settings_perset_novel option[value=${novelai_setting_names[nai_settings.preset_settings_novel]}]`
).attr("selected", "true");
//Load AI model config settings
amount_gen = settings.amount_gen;
@ -4924,10 +4931,11 @@ async function getSettings(type) {
showSwipeButtons();
// Kobold
loadKoboldSettings(settings);
loadKoboldSettings(settings.kai_settings ?? settings);
// Novel
loadNovelSettings(settings);
loadNovelSettings(settings.nai_settings ?? settings);
$(`#settings_perset_novel option[value=${novelai_setting_names[nai_settings.preset_settings_novel]}]`).attr("selected", "true");
// TextGen
loadTextGenSettings(data, settings);
@ -5047,8 +5055,8 @@ async function saveSettings(type) {
context_settings: context_settings,
tags: tags,
tag_map: tag_map,
...nai_settings,
...kai_settings,
nai_settings: nai_settings,
kai_settings: kai_settings,
...oai_settings,
}, null, 4),
beforeSend: function () {
@ -5080,7 +5088,23 @@ async function saveSettings(type) {
});
}
export function setGenerationParamsFromPreset(preset) {
if (preset.genamt !== undefined) {
amount_gen = preset.genamt;
$("#amount_gen").val(amount_gen);
$("#amount_gen_counter").text(`${amount_gen}`);
}
if (preset.max_length !== undefined) {
max_context = preset.max_length;
const needsUnlock = max_context > MAX_CONTEXT_DEFAULT;
$('#max_context_unlocked').prop('checked', needsUnlock).trigger('change');
$("#max_context").val(max_context);
$("#max_context_counter").text(`${max_context}`);
}
}
function setCharacterBlockHeight() {
const $children = $("#rm_print_characters_block").children();
@ -5591,11 +5615,19 @@ function onScenarioOverrideRemoveClick() {
$(this).closest('.scenario_override').find('.chat_scenario').val('').trigger('input');
}
function callPopup(text, type, inputValue = '', { okButton, rows } = {}) {
function callPopup(text, type, inputValue = '', { okButton, rows, wide, large } = {}) {
if (type) {
popup_type = type;
}
if (wide) {
$("#dialogue_popup").addClass("wide_dialogue_popup");
}
if (large) {
$("#dialogue_popup").addClass("large_dialogue_popup");
}
$("#dialogue_popup_cancel").css("display", "inline-block");
switch (popup_type) {
case "avatarToCrop":
@ -6840,6 +6872,73 @@ function doCharListDisplaySwitch() {
updateVisibleDivs('#rm_print_characters_block', true);
}
/**
* Function to handle the deletion of a character, given a specific popup type and character ID.
* If popup type equals "del_ch", it will proceed with deletion otherwise it will exit the function.
* It fetches the delete character route, sending necessary parameters, and in case of success,
* it proceeds to delete character from UI and saves settings.
* In case of error during the fetch request, it logs the error details.
*
* @param {string} popup_type - The type of popup currently active.
* @param {string} this_chid - The character ID to be deleted.
*/
export async function handleDeleteCharacter(popup_type, this_chid) {
if (popup_type !== "del_ch") {
return;
}
const delete_chats = !!$("#del_char_checkbox").prop("checked");
const avatar = characters[this_chid].avatar;
const name = characters[this_chid].name;
const msg = { avatar_url: avatar, delete_chats: delete_chats };
const response = await fetch('/deletecharacter', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(msg),
cache: 'no-cache',
});
if (response.ok) {
await deleteCharacter(name, avatar);
} else {
console.error('Failed to delete character: ', response.status, response.statusText);
}
}
/**
* Function to delete a character from UI after character deletion API success.
* It manages necessary UI changes such as closing advanced editing popup, unsetting
* character ID, resetting characters array and chat metadata, deselecting character's tab
* panel, removing character name from navigation tabs, clearing chat, removing character's
* avatar from tag_map, fetching updated list of characters and updating the 'deleted
* character' message.
* It also ensures to save the settings after all the operations.
*
* @param {string} name - The name of the character to be deleted.
* @param {string} avatar - The avatar URL of the character to be deleted.
*/
export async function deleteCharacter(name, avatar) {
$("#character_cross").click();
this_chid = "invalid-safety-id";
characters.length = 0;
name2 = systemUserName;
chat = [...safetychat];
chat_metadata = {};
setRightTabSelectedClass();
$(document.getElementById("rm_button_selected_ch")).children("h2").text("");
clearChat();
this_chid = undefined;
delete tag_map[avatar];
await getCharacters();
select_rm_info("char_delete", name);
printMessages();
saveSettingsDebounced();
}
$(document).ready(function () {
if (isMobile() === true) {
@ -7211,55 +7310,7 @@ $(document).ready(function () {
}, 200);
}
if (popup_type == "del_ch") {
console.log(
"Deleting character -- ChID: " +
this_chid +
" -- Name: " +
characters[this_chid].name
);
const delete_chats = !!$("#del_char_checkbox").prop("checked");
const avatar = characters[this_chid].avatar;
const name = characters[this_chid].name;
const msg = new FormData($("#form_create").get(0)); // ID form
msg.append("delete_chats", delete_chats);
jQuery.ajax({
method: "POST",
url: "/deletecharacter",
beforeSend: function () {
},
data: msg,
cache: false,
contentType: false,
processData: false,
success: async function (html) {
//RossAscends: New handling of character deletion that avoids page refreshes and should have
// fixed char corruption due to cache problems.
//due to how it is handled with 'popup_type', i couldn't find a way to make my method completely
// modular, so keeping it in TAI-main.js as a new default.
//this allows for dynamic refresh of character list after deleting a character.
// closes advanced editing popup
$("#character_cross").click();
// unsets expected chid before reloading (related to getCharacters/printCharacters from using old arrays)
this_chid = "invalid-safety-id";
// resets the characters array, forcing getcharacters to reset
characters.length = 0;
name2 = systemUserName; // replaces deleted charcter name with system user since she will be displayed next.
chat = [...safetychat]; // sets up system user to tell user about having deleted a character
chat_metadata = {}; // resets chat metadata
setRightTabSelectedClass() // 'deselects' character's tab panel
$(document.getElementById("rm_button_selected_ch"))
.children("h2")
.text(""); // removes character name from nav tabs
clearChat(); // removes deleted char's chat
this_chid = undefined; // prevents getCharacters from trying to load an invalid char.
delete tag_map[avatar]; // removes deleted char's avatar from tag_map
await getCharacters(); // gets the new list of characters (that doesn't include the deleted one)
select_rm_info("char_delete", name); // also updates the 'deleted character' message
printMessages(); // prints out system user's 'deleted character' message
//console.log("#dialogue_popup_ok(del-char) >>>> saving");
saveSettingsDebounced(); // saving settings to keep changes to variables
},
});
handleDeleteCharacter(popup_type, this_chid, characters);
}
if (popup_type == "alternate_greeting" && menu_type !== "create") {
createOrEditCharacter();
@ -7709,13 +7760,7 @@ $(document).ready(function () {
const preset = koboldai_settings[koboldai_setting_names[preset_settings]];
loadKoboldSettings(preset);
amount_gen = preset.genamt;
$("#amount_gen").val(amount_gen);
$("#amount_gen_counter").text(`${amount_gen}`);
max_context = preset.max_length;
$("#max_context").val(max_context);
$("#max_context_counter").text(`${max_context}`);
setGenerationParamsFromPreset(preset);
$("#range_block").find('input').prop("disabled", false);
$("#kobold-advanced-config").find('input').prop("disabled", false);
@ -8240,17 +8285,7 @@ $(document).ready(function () {
});
$("#dupe_button").click(async function () {
const body = { avatar_url: characters[this_chid].avatar };
const response = await fetch('/dupecharacter', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(body),
});
if (response.ok) {
toastr.success("Character Duplicated");
getCharacters();
}
await DupeChar();
});
$(document).on("click", ".select_chat_block, .bookmark_link, .mes_bookmark", async function () {

View File

@ -60,7 +60,10 @@ const extension_settings = {
dice: {},
regex: [],
tts: {},
sd: {},
sd: {
prompts: {},
character_prompts: {},
},
chromadb: {},
translate: {},
objective: {},

View File

@ -551,10 +551,10 @@ async function onSelectInjectFile(e) {
function doAutoAdjust(chat, maxContext) {
console.debug('CHROMADB: Auto-adjusting sliders (messages: %o, maxContext: %o)', chat.length, maxContext);
// Get mean message length
const meanMessageLength = chat.reduce((acc, cur) => acc + cur.mes.length, 0) / chat.length;
const meanMessageLength = chat.reduce((acc, cur) => acc + (cur?.mes?.length ?? 0), 0) / chat.length;
if (Number.isNaN(meanMessageLength)) {
console.debug('CHROMADB: Mean message length is NaN, aborting auto-adjust');
if (Number.isNaN(meanMessageLength) || meanMessageLength === 0) {
console.debug('CHROMADB: Mean message length is zero or NaN, aborting auto-adjust');
return;
}
@ -611,7 +611,7 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
console.debug("CHROMADB: Messages to store length vs keep context: %o vs %o", messagesToStore.length, extension_settings.chromadb.keep_context);
await addMessages(currentChatId, messagesToStore);
}
const lastMessage = chat[chat.length - 1];
let queriedMessages;
@ -811,15 +811,15 @@ jQuery(async () => {
<textarea id="chromadb_custom_msg" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_msg}" style="height: 61px; display: none;"></textarea>
<label for="chromadb_custom_depth" hidden><small>How deep should the memory messages be injected?: (<span id="chromadb_custom_depth_value"></span>)</small></label>
<input id="chromadb_custom_depth" type="range" min="${defaultSettings.chroma_depth_min}" max="${defaultSettings.chroma_depth_max}" step="${defaultSettings.chroma_depth_step}" value="${defaultSettings.chroma_depth}" hidden/>
<label for="chromadb_hhaa_wrapperfmt" hidden><small>Custom wrapper format:</small></label>
<textarea id="chromadb_hhaa_wrapperfmt" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_hhaa_wrapper}" style="height: 61px; display: none;"></textarea>
<label for="chromadb_hhaa_memoryfmt" hidden><small>Custom memory format:</small></label>
<textarea id="chromadb_hhaa_memoryfmt" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_hhaa_memory}" style="height: 61px; display: none;"></textarea>
<label for="chromadb_hhaa_token_limit" hidden><small>Maximum tokens allowed for memories: (<span id="chromadb_hhaa_token_limit_value"></span>)</small></label>
<input id="chromadb_hhaa_token_limit" type="range" min="0" max="2048" step="64" value="${defaultSettings.hhaa_token_limit}" hidden/>
<span>Memory Recall Strategy</span>
<select id="chromadb_recall_strategy">
<option value="original">Recall only from this chat</option>
@ -834,7 +834,7 @@ jQuery(async () => {
<input id="chromadb_keep_context" type="range" min="${defaultSettings.keep_context_min}" max="${defaultSettings.keep_context_max}" step="${defaultSettings.keep_context_step}" value="${defaultSettings.keep_context}" />
<label for="chromadb_n_results"><small>Maximum number of ChromaDB 'memories' to inject: (<span id="chromadb_n_results_value"></span>) messages</small></label>
<input id="chromadb_n_results" type="range" min="${defaultSettings.n_results_min}" max="${defaultSettings.n_results_max}" step="${defaultSettings.n_results_step}" value="${defaultSettings.n_results}" />
<label for="chromadb_keep_context_proportion"><small>Keep (<span id="chromadb_keep_context_proportion_value"></span>%) of in-context chat messages; replace the rest with memories</small></label>
<input id="chromadb_keep_context_proportion" type="range" min="${defaultSettings.keep_context_proportion_min}" max="${defaultSettings.keep_context_proportion_max}" step="${defaultSettings.keep_context_proportion_step}" value="${defaultSettings.keep_context_proportion}" />
<label for="chromadb_split_length"><small>Max length for each 'memory' pulled from the current chat history: (<span id="chromadb_split_length_value"></span>) characters</small></label>

View File

@ -8,10 +8,13 @@ import {
getRequestHeaders,
event_types,
eventSource,
appendImageToMessage
appendImageToMessage,
generateQuietPrompt,
this_chid,
} from "../../../script.js";
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment } from "../../utils.js";
import { selected_group } from "../../group-chats.js";
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename } from "../../utils.js";
export { MODULE_NAME };
// Wraps a string into monospace font-face span
@ -39,6 +42,15 @@ const generationMode = {
FREE: 6,
}
const modeLabels = {
[generationMode.CHARACTER]: 'Character ("Yourself")',
[generationMode.FACE]: 'Portrait ("Your Face")',
[generationMode.USER]: 'User ("Me")',
[generationMode.SCENARIO]: 'Scenario ("The Whole Story")',
[generationMode.NOW]: 'Last Message',
[generationMode.RAW_LAST]: 'Raw Last Message',
}
const triggerWords = {
[generationMode.CHARACTER]: ['you'],
[generationMode.USER]: ['me'],
@ -48,7 +60,7 @@ const triggerWords = {
[generationMode.FACE]: ['face'],
}
const quietPrompts = {
const promptTemplates = {
/*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */
[generationMode.CHARACTER]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,']",
//face-specific prompt
@ -134,6 +146,8 @@ const defaultSettings = {
// Refine mode
refine_mode: false,
prompts: promptTemplates,
}
async function loadSettings() {
@ -141,6 +155,14 @@ async function loadSettings() {
Object.assign(extension_settings.sd, defaultSettings);
}
if (extension_settings.sd.prompts === undefined) {
extension_settings.sd.prompts = promptTemplates;
}
if (extension_settings.sd.character_prompts === undefined) {
extension_settings.sd.character_prompts = {};
}
$('#sd_scale').val(extension_settings.sd.scale).trigger('input');
$('#sd_steps').val(extension_settings.sd.steps).trigger('input');
$('#sd_prompt_prefix').val(extension_settings.sd.prompt_prefix).trigger('input');
@ -154,9 +176,104 @@ async function loadSettings() {
$('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
$('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
addPromptTemplates();
await Promise.all([loadSamplers(), loadModels()]);
}
function addPromptTemplates() {
$('#sd_prompt_templates').empty();
for (const [name, prompt] of Object.entries(extension_settings.sd.prompts)) {
const label = $('<label></label>')
.text(modeLabels[name])
.attr('for', `sd_prompt_${name}`);
const textarea = $('<textarea></textarea>')
.addClass('textarea_compact text_pole')
.attr('id', `sd_prompt_${name}`)
.attr('rows', 6)
.val(prompt).on('input', () => {
extension_settings.sd.prompts[name] = textarea.val();
saveSettingsDebounced();
});
const button = $('<button></button>')
.addClass('menu_button fa-solid fa-undo')
.attr('title', 'Restore default')
.on('click', () => {
textarea.val(promptTemplates[name]);
extension_settings.sd.prompts[name] = promptTemplates[name];
saveSettingsDebounced();
});
const container = $('<div></div>')
.addClass('title_restorable')
.append(label)
.append(button)
$('#sd_prompt_templates').append(container);
$('#sd_prompt_templates').append(textarea);
}
}
async function refinePrompt(prompt) {
if (extension_settings.sd.refine_mode) {
const refinedPrompt = await callPopup('<h3>Review and edit the prompt:</h3>Press "Cancel" to abort the image generation.', 'input', prompt, { rows: 5, okButton: 'Generate' });
if (refinedPrompt) {
return refinedPrompt;
} else {
throw new Error('Generation aborted by user.');
}
}
return prompt;
}
function onChatChanged() {
if (this_chid === undefined || selected_group) {
$('#sd_character_prompt_block').hide();
return;
}
$('#sd_character_prompt_block').show();
const key = getCharaFilename(this_chid);
$('#sd_character_prompt').val(key ? (extension_settings.sd.character_prompts[key] || '') : '');
}
function onCharacterPromptInput() {
const key = getCharaFilename(this_chid);
extension_settings.sd.character_prompts[key] = $('#sd_character_prompt').val();
resetScrollHeight($(this));
saveSettingsDebounced();
}
function getCharacterPrefix() {
if (selected_group) {
return '';
}
const key = getCharaFilename(this_chid);
if (key) {
return extension_settings.sd.character_prompts[key] || '';
}
return '';
}
function combinePrefixes(str1, str2) {
if (!str2) {
return str1;
}
// Remove leading/trailing white spaces and commas from the strings
str1 = str1.trim().replace(/^,|,$/g, '');
str2 = str2.trim().replace(/^,|,$/g, '');
// Combine the strings with a comma between them
var result = `${str1}, ${str2},`;
return result;
}
function onRefineModeInput() {
extension_settings.sd.refine_mode = !!$('#sd_refine_mode').prop('checked');
saveSettingsDebounced();
@ -383,7 +500,7 @@ function getQuietPrompt(mode, trigger) {
return trigger;
}
return substituteParams(stringFormat(quietPrompts[mode], trigger));
return substituteParams(stringFormat(extension_settings.sd.prompts[mode], trigger));
}
function processReply(str) {
@ -475,31 +592,16 @@ async function getPrompt(generationType, message, trigger, quiet_prompt) {
break;
}
if (generationType !== generationMode.FREE) {
prompt = await refinePrompt(prompt);
}
return prompt;
}
async function generatePrompt(quiet_prompt) {
let reply = processReply(await new Promise(
async function promptPromise(resolve, reject) {
try {
await getContext().generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
}
catch {
reject();
}
}));
if (extension_settings.sd.refine_mode) {
const refinedPrompt = await callPopup('<h3>Review and edit the generated prompt:</h3>Press "Cancel" to abort the image generation.', 'input', reply, { rows: 5, okButton: 'Generate' });
if (refinedPrompt) {
reply = refinedPrompt;
} else {
throw new Error('Generation aborted by user.');
}
}
return reply;
const reply = await generateQuietPrompt(quiet_prompt);
return processReply(reply);
}
async function sendGenerationRequest(prompt, callback) {
@ -524,7 +626,7 @@ async function generateExtrasImage(prompt, callback) {
scale: extension_settings.sd.scale,
width: extension_settings.sd.width,
height: extension_settings.sd.height,
prompt_prefix: extension_settings.sd.prompt_prefix,
prompt_prefix: combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()),
negative_prompt: extension_settings.sd.negative_prompt,
restore_faces: !!extension_settings.sd.restore_faces,
enable_hr: !!extension_settings.sd.enable_hr,
@ -552,7 +654,7 @@ async function generateHordeImage(prompt, callback) {
scale: extension_settings.sd.scale,
width: extension_settings.sd.width,
height: extension_settings.sd.height,
prompt_prefix: extension_settings.sd.prompt_prefix,
prompt_prefix: combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()),
negative_prompt: extension_settings.sd.negative_prompt,
model: extension_settings.sd.model,
nsfw: extension_settings.sd.horde_nsfw,
@ -690,7 +792,9 @@ async function sdMessageButton(e) {
try {
setBusyIcon(true);
if (hasSavedImage) {
const prompt = message?.extra?.title;
const prompt = await refinePrompt(message.extra.title);
message.extra.title = prompt;
console.log('Regenerating an image, using existing prompt:', prompt);
await sendGenerationRequest(prompt, saveGeneratedImage);
}
@ -763,60 +867,74 @@ jQuery(async () => {
<div class="sd_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Stable Diffusion</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
<b>Stable Diffusion</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
<br>
<small><i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i></small>
<label for="sd_refine_mode" class="checkbox_label" title="Allow to edit prompts manually before sending them to generation API">
<input id="sd_refine_mode" type="checkbox" />
Edit prompts before generation
</label>
<div class="flex-container flexGap5 marginTop10 margin-bot-10px">
<label class="checkbox_label">
<input id="sd_horde" type="checkbox" />
Use Stable Horde
</label>
<label style="margin-left:1em;" class="checkbox_label">
<input id="sd_horde_nsfw" type="checkbox" />
Allow NSFW images from Horde
</label>
</div>
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
<input id="sd_scale" type="range" min="${defaultSettings.scale_min}" max="${defaultSettings.scale_max}" step="${defaultSettings.scale_step}" value="${defaultSettings.scale}" />
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
<input id="sd_steps" type="range" min="${defaultSettings.steps_min}" max="${defaultSettings.steps_max}" step="${defaultSettings.steps_step}" value="${defaultSettings.steps}" />
<label for="sd_width">Width (<span id="sd_width_value"></span>)</label>
<input id="sd_width" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.width}" />
<label for="sd_height">Height (<span id="sd_height_value"></span>)</label>
<input id="sd_height" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.height}" />
<div><small>Only for Horde or remote Stable Diffusion Web UI:</small></div>
<div class="flex-container marginTop10 margin-bot-10px">
<label class="flex1 checkbox_label">
<input id="sd_restore_faces" type="checkbox" />
Restore Faces
</label>
<label class="flex1 checkbox_label">
<input id="sd_enable_hr" type="checkbox" />
Hires. Fix
</label>
</div>
<label for="sd_model">Stable Diffusion model</label>
<select id="sd_model"></select>
<label for="sd_sampler">Sampling method</label>
<select id="sd_sampler"></select>
<div class="flex-container flexGap5 margin-bot-10px">
<label class="checkbox_label">
<input id="sd_horde_karras" type="checkbox" />
Karras (only for Horde, not all samplers supported)
</label>
</div>
<label for="sd_prompt_prefix">Common prompt prefix</label>
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="3"></textarea>
<div id="sd_character_prompt_block">
<label for="sd_character_prompt">Character-specific prompt prefix</label>
<small>Won't be used in groups.</small>
<textarea id="sd_character_prompt" class="text_pole textarea_compact" rows="3" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prefix.&#10;Example: female, green eyes, brown hair, pink shirt"></textarea>
</div>
<label for="sd_negative_prompt">Negative prompt</label>
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="3"></textarea>
</div>
</div>
<div class="inline-drawer-content">
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
<br>
<small><i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i></small>
<label for="sd_refine_mode" class="checkbox_label" title="Allow to edit prompts manually before sending them to generation API">
<input id="sd_refine_mode" type="checkbox" />
Edit prompts before generation
</label>
<div class="flex-container flexGap5 marginTop10 margin-bot-10px">
<label class="checkbox_label">
<input id="sd_horde" type="checkbox" />
Use Stable Horde
</label>
<label style="margin-left:1em;" class="checkbox_label">
<input id="sd_horde_nsfw" type="checkbox" />
Allow NSFW images from Horde
</label>
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>SD Prompt Templates</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
<input id="sd_scale" type="range" min="${defaultSettings.scale_min}" max="${defaultSettings.scale_max}" step="${defaultSettings.scale_step}" value="${defaultSettings.scale}" />
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
<input id="sd_steps" type="range" min="${defaultSettings.steps_min}" max="${defaultSettings.steps_max}" step="${defaultSettings.steps_step}" value="${defaultSettings.steps}" />
<label for="sd_width">Width (<span id="sd_width_value"></span>)</label>
<input id="sd_width" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.width}" />
<label for="sd_height">Height (<span id="sd_height_value"></span>)</label>
<input id="sd_height" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.height}" />
<div><small>Only for Horde or remote Stable Diffusion Web UI:</small></div>
<div class="flex-container marginTop10 margin-bot-10px">
<label class="flex1 checkbox_label">
<input id="sd_restore_faces" type="checkbox" />
Restore Faces
</label>
<label class="flex1 checkbox_label">
<input id="sd_enable_hr" type="checkbox" />
Hires. Fix
</label>
<div id="sd_prompt_templates" class="inline-drawer-content">
</div>
<label for="sd_model">Stable Diffusion model</label>
<select id="sd_model"></select>
<label for="sd_sampler">Sampling method</label>
<select id="sd_sampler"></select>
<div class="flex-container flexGap5 margin-bot-10px">
<label class="checkbox_label">
<input id="sd_horde_karras" type="checkbox" />
Karras (only for Horde, not all samplers supported)
</label>
</div>
<label for="sd_prompt_prefix">Generated prompt prefix</label>
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="2"></textarea>
<label for="sd_negative_prompt">Negative prompt</label>
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="2"></textarea>
</div>
</div>`;
@ -835,16 +953,21 @@ jQuery(async () => {
$('#sd_restore_faces').on('input', onRestoreFacesInput);
$('#sd_enable_hr').on('input', onHighResFixInput);
$('#sd_refine_mode').on('input', onRefineModeInput);
$('#sd_character_prompt').on('input', onCharacterPromptInput);
$('#sd_character_prompt_block').hide();
$('.sd_settings .inline-drawer-toggle').on('click', function () {
initScrollHeight($("#sd_prompt_prefix"));
initScrollHeight($("#sd_negative_prompt"));
initScrollHeight($("#sd_character_prompt"));
})
eventSource.on(event_types.EXTRAS_CONNECTED, async () => {
await Promise.all([loadSamplers(), loadModels()]);
});
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
await loadSettings();
$('body').addClass('sd');
});

View File

@ -24,6 +24,6 @@
}
#sd_dropdown {
z-index: 3000;
z-index: 30000;
backdrop-filter: blur(--SmartThemeBlurStrength);
}
}

View File

@ -11,13 +11,18 @@ export {
};
const nai_settings = {
temp_novel: 0.5,
rep_pen_novel: 1,
rep_pen_size_novel: 100,
rep_pen_slope_novel: 0,
rep_pen_freq_novel: 0,
rep_pen_presence_novel: 0,
tail_free_sampling_novel: 0.68,
temperature: 0.5,
repetition_penalty: 1,
repetition_penalty_range: 100,
repetition_penalty_slope: 0,
repetition_penalty_frequency: 0,
repetition_penalty_presence: 0,
tail_free_sampling: 0.68,
top_k: 0,
top_p: 1,
top_a: 1,
typical_p: 1,
min_length: 0,
model_novel: "euterpe-v2",
preset_settings_novel: "Classic-Euterpe",
streaming_novel: false,
@ -45,13 +50,18 @@ function loadNovelPreset(preset) {
$("#max_context_counter").text(`${preset.max_context}`);
$("#rep_pen_size_novel").attr('max', preset.max_context);
nai_settings.temp_novel = preset.temperature;
nai_settings.rep_pen_novel = preset.repetition_penalty;
nai_settings.rep_pen_size_novel = preset.repetition_penalty_range;
nai_settings.rep_pen_slope_novel = preset.repetition_penalty_slope;
nai_settings.rep_pen_freq_novel = preset.repetition_penalty_frequency;
nai_settings.rep_pen_presence_novel = preset.repetition_penalty_presence;
nai_settings.tail_free_sampling_novel = preset.tail_free_sampling;
nai_settings.temperature = preset.temperature;
nai_settings.repetition_penalty = preset.repetition_penalty;
nai_settings.repetition_penalty_range = preset.repetition_penalty_range;
nai_settings.repetition_penalty_slope = preset.repetition_penalty_slope;
nai_settings.repetition_penalty_frequency = preset.repetition_penalty_frequency;
nai_settings.repetition_penalty_presence = preset.repetition_penalty_presence;
nai_settings.tail_free_sampling = preset.tail_free_sampling;
nai_settings.top_k = preset.top_k;
nai_settings.top_p = preset.top_p;
nai_settings.top_a = preset.top_a;
nai_settings.typical_p = preset.typical_p;
nai_settings.min_length = preset.min_length;
loadNovelSettingsUi(nai_settings);
}
@ -59,33 +69,58 @@ function loadNovelSettings(settings) {
//load the rest of the Novel settings without any checks
nai_settings.model_novel = settings.model_novel;
$(`#model_novel_select option[value=${nai_settings.model_novel}]`).attr("selected", true);
$('#model_novel_select').val(nai_settings.model_novel);
nai_settings.temp_novel = settings.temp_novel;
nai_settings.rep_pen_novel = settings.rep_pen_novel;
nai_settings.rep_pen_size_novel = settings.rep_pen_size_novel;
nai_settings.rep_pen_slope_novel = settings.rep_pen_slope_novel;
nai_settings.rep_pen_freq_novel = settings.rep_pen_freq_novel;
nai_settings.rep_pen_presence_novel = settings.rep_pen_presence_novel;
nai_settings.tail_free_sampling_novel = settings.tail_free_sampling_novel;
nai_settings.preset_settings_novel = settings.preset_settings_novel;
nai_settings.temperature = settings.temperature;
nai_settings.repetition_penalty = settings.repetition_penalty;
nai_settings.repetition_penalty_range = settings.repetition_penalty_range;
nai_settings.repetition_penalty_slope = settings.repetition_penalty_slope;
nai_settings.repetition_penalty_frequency = settings.repetition_penalty_frequency;
nai_settings.repetition_penalty_presence = settings.repetition_penalty_presence;
nai_settings.tail_free_sampling = settings.tail_free_sampling;
nai_settings.top_k = settings.top_k;
nai_settings.top_p = settings.top_p;
nai_settings.top_a = settings.top_a;
nai_settings.typical_p = settings.typical_p;
nai_settings.min_length = settings.min_length;
nai_settings.streaming_novel = !!settings.streaming_novel;
loadNovelSettingsUi(nai_settings);
// reload the preset to migrate any new settings
for (const key of Object.keys(nai_settings)) {
if (typeof nai_settings[key] === 'number' && Number.isNaN(nai_settings[key])) {
$("#settings_perset_novel").trigger("change");
}
}
}
function loadNovelSettingsUi(ui_settings) {
$("#temp_novel").val(ui_settings.temp_novel);
$("#temp_counter_novel").text(Number(ui_settings.temp_novel).toFixed(2));
$("#rep_pen_novel").val(ui_settings.rep_pen_novel);
$("#rep_pen_counter_novel").text(Number(ui_settings.rep_pen_novel).toFixed(2));
$("#rep_pen_size_novel").val(ui_settings.rep_pen_size_novel);
$("#rep_pen_size_counter_novel").text(Number(ui_settings.rep_pen_size_novel).toFixed(0));
$("#rep_pen_slope_novel").val(ui_settings.rep_pen_slope_novel);
$("#rep_pen_slope_counter_novel").text(Number(`${ui_settings.rep_pen_slope_novel}`).toFixed(2));
$("#rep_pen_freq_novel").val(ui_settings.rep_pen_freq_novel);
$("#rep_pen_freq_counter_novel").text(Number(ui_settings.rep_pen_freq_novel).toFixed(5));
$("#rep_pen_presence_novel").val(ui_settings.rep_pen_presence_novel);
$("#rep_pen_presence_counter_novel").text(Number(ui_settings.rep_pen_presence_novel).toFixed(3));
$("#tail_free_sampling_novel").val(ui_settings.tail_free_sampling_novel);
$("#tail_free_sampling_counter_novel").text(Number(ui_settings.tail_free_sampling_novel).toFixed(3));
$("#temp_novel").val(ui_settings.temperature);
$("#temp_counter_novel").text(Number(ui_settings.temperature).toFixed(2));
$("#rep_pen_novel").val(ui_settings.repetition_penalty);
$("#rep_pen_counter_novel").text(Number(ui_settings.repetition_penalty).toFixed(2));
$("#rep_pen_size_novel").val(ui_settings.repetition_penalty_range);
$("#rep_pen_size_counter_novel").text(Number(ui_settings.repetition_penalty_range).toFixed(0));
$("#rep_pen_slope_novel").val(ui_settings.repetition_penalty_slope);
$("#rep_pen_slope_counter_novel").text(Number(`${ui_settings.repetition_penalty_slope}`).toFixed(2));
$("#rep_pen_freq_novel").val(ui_settings.repetition_penalty_frequency);
$("#rep_pen_freq_counter_novel").text(Number(ui_settings.repetition_penalty_frequency).toFixed(5));
$("#rep_pen_presence_novel").val(ui_settings.repetition_penalty_presence);
$("#rep_pen_presence_counter_novel").text(Number(ui_settings.repetition_penalty_presence).toFixed(3));
$("#tail_free_sampling_novel").val(ui_settings.tail_free_sampling);
$("#tail_free_sampling_counter_novel").text(Number(ui_settings.tail_free_sampling).toFixed(3));
$("#top_k_novel").val(ui_settings.top_k);
$("#top_k_counter_novel").text(Number(ui_settings.top_k).toFixed(0));
$("#top_p_novel").val(ui_settings.top_p);
$("#top_p_counter_novel").text(Number(ui_settings.top_p).toFixed(2));
$("#top_a_novel").val(ui_settings.top_a);
$("#top_a_counter_novel").text(Number(ui_settings.top_a).toFixed(2));
$("#typical_p_novel").val(ui_settings.typical_p);
$("#typical_p_counter_novel").text(Number(ui_settings.typical_p).toFixed(2));
$("#min_length_novel").val(ui_settings.min_length);
$("#min_length_counter_novel").text(Number(ui_settings.min_length).toFixed(0));
$("#streaming_novel").prop('checked', ui_settings.streaming_novel);
}
@ -94,43 +129,73 @@ const sliders = [
sliderId: "#temp_novel",
counterId: "#temp_counter_novel",
format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.temp_novel = Number(val).toFixed(2); },
setValue: (val) => { nai_settings.temperature = Number(val).toFixed(2); },
},
{
sliderId: "#rep_pen_novel",
counterId: "#rep_pen_counter_novel",
format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.rep_pen_novel = Number(val).toFixed(2); },
setValue: (val) => { nai_settings.repetition_penalty = Number(val).toFixed(2); },
},
{
sliderId: "#rep_pen_size_novel",
counterId: "#rep_pen_size_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.rep_pen_size_novel = Number(val).toFixed(0); },
setValue: (val) => { nai_settings.repetition_penalty_range = Number(val).toFixed(0); },
},
{
sliderId: "#rep_pen_slope_novel",
counterId: "#rep_pen_slope_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.rep_pen_slope_novel = Number(val).toFixed(2); },
setValue: (val) => { nai_settings.repetition_penalty_slope = Number(val).toFixed(2); },
},
{
sliderId: "#rep_pen_freq_novel",
counterId: "#rep_pen_freq_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.rep_pen_freq_novel = Number(val).toFixed(5); },
setValue: (val) => { nai_settings.repetition_penalty_frequency = Number(val).toFixed(5); },
},
{
sliderId: "#rep_pen_presence_novel",
counterId: "#rep_pen_presence_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.rep_pen_presence_novel = Number(val).toFixed(3); },
setValue: (val) => { nai_settings.repetition_penalty_presence = Number(val).toFixed(3); },
},
{
sliderId: "#tail_free_sampling_novel",
counterId: "#tail_free_sampling_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.tail_free_sampling_novel = Number(val).toFixed(3); },
setValue: (val) => { nai_settings.tail_free_sampling = Number(val).toFixed(3); },
},
{
sliderId: "#top_k_novel",
counterId: "#top_k_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.top_k = Number(val).toFixed(0); },
},
{
sliderId: "#top_p_novel",
counterId: "#top_p_counter_novel",
format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.top_p = Number(val).toFixed(2); },
},
{
sliderId: "#top_a_novel",
counterId: "#top_a_counter_novel",
format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.top_a = Number(val).toFixed(2); },
},
{
sliderId: "#typical_p_novel",
counterId: "#typical_p_counter_novel",
format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.typical_p = Number(val).toFixed(2); },
},
{
sliderId: "#min_length_novel",
counterId: "#min_length_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.min_length = Number(val).toFixed(0); },
},
];
@ -139,19 +204,19 @@ export function getNovelGenerationData(finalPromt, this_settings, this_amount_ge
"input": finalPromt,
"model": nai_settings.model_novel,
"use_string": true,
"temperature": parseFloat(nai_settings.temp_novel),
"temperature": parseFloat(nai_settings.temperature),
"max_length": this_amount_gen, // this_settings.max_length, // <= why?
"min_length": this_settings.min_length,
"tail_free_sampling": parseFloat(nai_settings.tail_free_sampling_novel),
"repetition_penalty": parseFloat(nai_settings.rep_pen_novel),
"repetition_penalty_range": parseInt(nai_settings.rep_pen_size_novel),
"repetition_penalty_slope": parseFloat(nai_settings.rep_pen_slope_novel),
"repetition_penalty_frequency": parseFloat(nai_settings.rep_pen_freq_novel),
"repetition_penalty_presence": parseFloat(nai_settings.rep_pen_presence_novel),
"top_a": this_settings.top_a,
"top_p": this_settings.top_p,
"top_k": this_settings.top_k,
"typical_p": this_settings.typical_p,
"min_length": parseInt(nai_settings.min_length),
"tail_free_sampling": parseFloat(nai_settings.tail_free_sampling),
"repetition_penalty": parseFloat(nai_settings.repetition_penalty),
"repetition_penalty_range": parseInt(nai_settings.repetition_penalty_range),
"repetition_penalty_slope": parseFloat(nai_settings.repetition_penalty_slope),
"repetition_penalty_frequency": parseFloat(nai_settings.repetition_penalty_frequency),
"repetition_penalty_presence": parseFloat(nai_settings.repetition_penalty_presence),
"top_a": parseFloat(nai_settings.top_a),
"top_p": parseFloat(nai_settings.top_p),
"top_k": parseInt(nai_settings.top_k),
"typical_p": parseFloat(nai_settings.typical_p),
//"stop_sequences": {{187}},
//bad_words_ids = {{50256}, {0}, {1}};
"generate_until_sentence": true,
@ -212,7 +277,7 @@ $(document).ready(function () {
const value = $(this).val();
const formattedValue = slider.format(value);
slider.setValue(value);
$(slider.counterId).html(formattedValue);
$(slider.counterId).text(formattedValue);
console.log('saving');
saveSettingsDebounced();
});

View File

@ -43,7 +43,7 @@ export {
send_on_enter_options,
};
const MAX_CONTEXT_DEFAULT = 4096;
export const MAX_CONTEXT_DEFAULT = 4096;
const MAX_CONTEXT_UNLOCKED = 65536;
const avatar_styles = {
@ -534,6 +534,11 @@ async function applyTheme(name) {
{
key: 'chat_width',
action: async () => {
// If chat width is not set, set it to 50
if (!power_user.chat_width) {
power_user.chat_width = 50;
}
localStorage.setItem(storage_keys.chat_width, power_user.chat_width);
applyChatWidth();
}
@ -660,6 +665,10 @@ function loadPowerUserSettings(settings, data) {
power_user.waifuMode = false;
}
if (power_user.chat_width === '') {
power_user.chat_width = 50;
}
$('#trim_spaces').prop("checked", power_user.trim_spaces);
$('#continue_on_send').prop("checked", power_user.continue_on_send);
$('#auto_swipe').prop("checked", power_user.auto_swipe);
@ -1151,10 +1160,10 @@ async function resetMovablePanels(type) {
//if happening as part of preset application, do it quietly.
if (type === 'quiet') {
return
//if happening due to resize, tell user.
//if happening due to resize, tell user.
} else if (type === 'resize') {
toastr.warning('Panel positions reset due to zoom/resize');
//if happening due to manual button press
//if happening due to manual button press
} else {
toastr.success('Panel positions reset');
}
@ -1173,7 +1182,7 @@ function doNewChat() {
function doRandomChat() {
resetSelectedGroup();
setCharacterId(Math.floor(Math.random() * characters.length));
setCharacterId(Math.floor(Math.random() * characters.length).toString());
setTimeout(() => {
reloadCurrentChat();
}, 1);

View File

@ -0,0 +1,351 @@
import {
amount_gen,
callPopup,
characters,
eventSource,
event_types,
getRequestHeaders,
koboldai_setting_names,
koboldai_settings,
main_api,
max_context,
nai_settings,
novelai_setting_names,
novelai_settings,
saveSettingsDebounced,
this_chid,
} from "../script.js";
import { groups, selected_group } from "./group-chats.js";
import { kai_settings } from "./kai-settings.js";
import {
textgenerationwebui_preset_names,
textgenerationwebui_presets,
textgenerationwebui_settings,
} from "./textgen-settings.js";
import { download, parseJsonFile, waitUntilCondition } from "./utils.js";
const presetManagers = {};
function autoSelectPreset() {
const presetManager = getPresetManager();
if (!presetManager) {
console.debug(`Preset Manager not found for API: ${main_api}`);
return;
}
const name = selected_group ? groups.find(x => x.id == selected_group)?.name : characters[this_chid]?.name;
if (!name) {
console.debug(`Preset candidate not found for API: ${main_api}`);
return;
}
const preset = presetManager.findPreset(name);
const selectedPreset = presetManager.getSelectedPreset();
if (preset === selectedPreset) {
console.debug(`Preset already selected for API: ${main_api}, name: ${name}`);
return;
}
if (preset !== undefined && preset !== null) {
console.log(`Preset found for API: ${main_api}, name: ${name}`);
presetManager.selectPreset(preset);
}
}
function getPresetManager() {
const apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
if (!Object.keys(presetManagers).includes(apiId)) {
return null;
}
return presetManagers[apiId];
}
function registerPresetManagers() {
$('select[data-preset-manager-for]').each((_, e) => {
const forData = $(e).data("preset-manager-for");
for (const apiId of forData.split(",")) {
console.debug(`Registering preset manager for API: ${apiId}`);
presetManagers[apiId] = new PresetManager($(e), apiId);
}
});
}
class PresetManager {
constructor(select, apiId) {
this.select = select;
this.apiId = apiId;
}
findPreset(name) {
return $(this.select).find(`option:contains(${name})`).val();
}
getSelectedPreset() {
return $(this.select).find("option:selected").val();
}
getSelectedPresetName() {
return $(this.select).find("option:selected").text();
}
selectPreset(preset) {
$(this.select).find(`option[value=${preset}]`).prop('selected', true);
$(this.select).val(preset).trigger("change");
}
async updatePreset() {
const selected = $(this.select).find("option:selected");
if (selected.val() == 'gui') {
toastr.info('Cannot update GUI preset');
return;
}
const name = selected.text();
await this.savePreset(name);
toastr.success('Preset updated');
}
async savePresetAs() {
const popupText = `
<h3>Preset name:</h3>
<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>`;
const name = await callPopup(popupText, "input");
await this.savePreset(name);
toastr.success('Preset saved');
}
async savePreset(name, settings) {
const preset = settings ?? this.getPresetSettings();
const res = await fetch(`/save_preset`, {
method: "POST",
headers: getRequestHeaders(),
body: JSON.stringify({ preset, name, apiId: this.apiId })
});
if (!res.ok) {
toastr.error('Failed to save preset');
}
const data = await res.json();
name = data.name;
this.updateList(name, preset);
}
getPresetList() {
let presets = [];
let preset_names = {};
switch (this.apiId) {
case "koboldhorde":
case "kobold":
presets = koboldai_settings;
preset_names = koboldai_setting_names;
break;
case "novel":
presets = novelai_settings;
preset_names = novelai_setting_names;
break;
case "textgenerationwebui":
presets = textgenerationwebui_presets;
preset_names = textgenerationwebui_preset_names;
break;
default:
console.warn(`Unknown API ID ${this.apiId}`);
}
return { presets, preset_names };
}
updateList(name, preset) {
const { presets, preset_names } = this.getPresetList();
const presetExists = this.apiId == "textgenerationwebui" ? preset_names.includes(name) : Object.keys(preset_names).includes(name);
if (presetExists) {
if (this.apiId == "textgenerationwebui") {
presets[preset_names.indexOf(name)] = preset;
$(this.select).find(`option[value="${name}"]`).prop('selected', true);
$(this.select).val(name).trigger("change");
}
else {
const value = preset_names[name];
presets[value] = preset;
$(this.select).find(`option[value="${value}"]`).prop('selected', true);
$(this.select).val(value).trigger("change");
}
}
else {
presets.push(preset);
const value = presets.length - 1;
// ooba is reversed
if (this.apiId == "textgenerationwebui") {
preset_names[value] = name;
const option = $('<option></option>', { value: name, text: name, selected: true });
$(this.select).append(option);
$(this.select).val(name).trigger("change");
} else {
preset_names[name] = value;
const option = $('<option></option>', { value: value, text: name, selected: true });
$(this.select).append(option);
$(this.select).val(value).trigger("change");
}
}
}
getPresetSettings() {
function getSettingsByApiId(apiId) {
switch (apiId) {
case "koboldhorde":
case "kobold":
return kai_settings;
case "novel":
return nai_settings;
case "textgenerationwebui":
return textgenerationwebui_settings;
default:
console.warn(`Unknown API ID ${apiId}`);
return {};
}
}
const filteredKeys = ['preset', 'streaming_url', 'stopping_strings', 'use_stop_sequence'];
const settings = Object.assign({}, getSettingsByApiId(this.apiId));
for (const key of filteredKeys) {
if (settings.hasOwnProperty(key)) {
delete settings[key];
}
}
settings['genamt'] = amount_gen;
settings['max_length'] = max_context;
return settings;
}
async deleteCurrentPreset() {
const { presets, preset_names } = this.getPresetList();
const value = this.getSelectedPreset();
const nameToDelete = this.getSelectedPresetName();
if (value == 'gui') {
toastr.info('Cannot delete GUI preset');
return;
}
$(this.select).find(`option[value="${value}"]`).remove();
if (this.apiId == "textgenerationwebui") {
preset_names.splice(preset_names.indexOf(value), 1);
} else {
delete preset_names[nameToDelete];
}
if (Object.keys(preset_names).length) {
const nextPresetName = Object.keys(preset_names)[0];
const newValue = preset_names[nextPresetName];
$(this.select).find(`option[value="${newValue}"]`).attr('selected', true);
$(this.select).trigger('change');
}
const response = await fetch('/delete_preset', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ name: nameToDelete, apiId: this.apiId }),
});
if (!response.ok) {
toastr.warning('Preset was not deleted from server');
} else {
toastr.success('Preset deleted');
}
}
}
jQuery(async () => {
await waitUntilCondition(() => eventSource !== undefined);
eventSource.on(event_types.CHAT_CHANGED, autoSelectPreset);
registerPresetManagers();
$(document).on("click", "[data-preset-manager-update]", async function () {
const presetManager = getPresetManager();
if (!presetManager) {
return;
}
await presetManager.updatePreset();
});
$(document).on("click", "[data-preset-manager-new]", async function () {
const presetManager = getPresetManager();
if (!presetManager) {
return;
}
await presetManager.savePresetAs();
});
$(document).on("click", "[data-preset-manager-export]", async function () {
const presetManager = getPresetManager();
if (!presetManager) {
return;
}
const selected = $(presetManager.select).find("option:selected");
const name = selected.text();
const preset = presetManager.getPresetSettings();
const data = JSON.stringify(preset, null, 4);
download(data, `${name}.json`, "application/json");
});
$(document).on("click", "[data-preset-manager-import]", async function () {
$('[data-preset-manager-file]').trigger('click');
});
$(document).on("change", "[data-preset-manager-file]", async function (e) {
const presetManager = getPresetManager();
if (!presetManager) {
return;
}
const file = e.target.files[0];
if (!file) {
return;
}
const name = file.name.replace('.json', '').replace('.settings', '');
const data = await parseJsonFile(file);
await presetManager.savePreset(name, data);
toastr.success('Preset imported');
e.target.value = null;
});
$(document).on("click", "[data-preset-manager-delete]", async function () {
const presetManager = getPresetManager();
if (!presetManager) {
return;
}
const confirm = await callPopup('Delete the preset? This action is irreversible and your current settings will be overwritten.', 'confirm');
if (!confirm) {
return;
}
await presetManager.deleteCurrentPreset();
saveSettingsDebounced();
});
})

View File

@ -192,7 +192,7 @@ function goToCharacterCallback(_, name) {
const characterIndex = findCharacterIndex(name);
if (characterIndex !== -1) {
openChat(characterIndex);
openChat(new String(characterIndex));
} else {
console.warn(`No matches found for name "${name}"`);
}

View File

@ -3,6 +3,7 @@ import {
getStoppingStrings,
max_context,
saveSettingsDebounced,
setGenerationParamsFromPreset,
} from "../script.js";
export {
@ -11,7 +12,7 @@ export {
generateTextGenWithStreaming,
}
let textgenerationwebui_settings = {
const textgenerationwebui_settings = {
temp: 0.7,
top_p: 0.5,
top_k: 40,
@ -44,8 +45,8 @@ let textgenerationwebui_settings = {
mirostat_eta: 0.1,
};
let textgenerationwebui_presets = [];
let textgenerationwebui_preset_names = [];
export let textgenerationwebui_presets = [];
export let textgenerationwebui_preset_names = [];
const setting_names = [
"temp",
@ -89,6 +90,7 @@ function selectPreset(name) {
const value = preset[name];
setSettingByName(name, value, true);
}
setGenerationParamsFromPreset(preset);
saveSettingsDebounced();
}

View File

@ -4500,14 +4500,14 @@ toolcool-color-picker {
flex-direction: column;
}
.openai_preset_buttons {
.openai_preset_buttons, .preset_buttons {
display: flex;
flex-direction: row;
align-items: baseline;
gap: 5px;
}
.openai_preset_buttons select {
.openai_preset_buttons select, .preset_buttons select {
flex-grow: 1;
}
@ -5422,4 +5422,4 @@ body.waifuMode .zoomed_avatar {
background-color: var(--SmartThemeBlurTintColor);
text-align: center;
line-height: 14px;
}
}

View File

@ -1108,7 +1108,7 @@ app.post("/editcharacterattribute", jsonParser, async function (request, respons
}
});
app.post("/deletecharacter", urlencodedParser, async function (request, response) {
app.post("/deletecharacter", jsonParser, async function (request, response) {
if (!request.body || !request.body.avatar_url) {
return response.sendStatus(400);
}
@ -3341,6 +3341,47 @@ app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_op
response_tokenize_openai.send({ "token_count": num_tokens });
});
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);
if (!directory) {
return response.sendStatus(400);
}
const fullpath = path.join(directory, filename);
fs.writeFileSync(fullpath, JSON.stringify(request.body.preset, null, 4), 'utf-8');
return response.send({ name });
});
app.post("/delete_preset", jsonParser, function (request, response) {
const name = sanitize(request.body.name);
if (!name) {
return response.sendStatus(400);
}
const filename = `${name}.settings`;
const directory = getPresetFolderByApiId(request.body.apiId);
if (!directory) {
return response.sendStatus(400);
}
const fullpath = path.join(directory, filename);
if (fs.existsSync) {
fs.unlinkSync(fullpath);
return response.sendStatus(200);
} else {
return response.sendStatus(404);
}
});
app.post("/savepreset_openai", jsonParser, function (request, response) {
const name = sanitize(request.query.name);
if (!request.body || !name) {
@ -3353,6 +3394,20 @@ app.post("/savepreset_openai", jsonParser, function (request, response) {
return response.send({ name });
});
function getPresetFolderByApiId(apiId) {
switch (apiId) {
case 'kobold':
case 'koboldhorde':
return directories.koboldAI_Settings;
case 'novel':
return directories.novelAI_Settings;
case 'textgenerationwebui':
return directories.textGen_Settings;
default:
return null;
}
}
function createTokenizationHandler(getTokenizerFn) {
return async function (request, response) {
if (!request.body) {