diff --git a/package-lock.json b/package-lock.json index 695aa3044..ea8a4457f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sillytavern", - "version": "1.5.3", + "version": "1.5.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sillytavern", - "version": "1.5.3", + "version": "1.5.4", "license": "AGPL-3.0", "dependencies": { "@dqbd/tiktoken": "^1.0.2", @@ -20,6 +20,7 @@ "exifreader": "^4.12.0", "express": "^4.18.2", "gpt3-tokenizer": "^1.1.5", + "ip-matching": "^2.1.2", "ipaddr.js": "^2.0.1", "jimp": "^0.22.7", "jquery": "^3.6.4", @@ -34,6 +35,7 @@ "png-chunks-extract": "^1.0.0", "rimraf": "^3.0.2", "sanitize-filename": "^1.6.3", + "uniqolor": "^1.1.0", "webp-converter": "2.3.2", "ws": "^8.13.0", "yargs": "^17.7.1" @@ -1657,6 +1659,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ip-matching": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ip-matching/-/ip-matching-2.1.2.tgz", + "integrity": "sha512-/ok+VhKMasgR5gvTRViwRFQfc0qYt9Vdowg6TO4/pFlDCob5ZjGPkwuOoQVCd5OrMm20zqh+1vA8KLJZTeWudg==" + }, "node_modules/ipaddr.js": { "version": "2.0.1", "license": "MIT", @@ -3084,6 +3091,11 @@ "version": "0.0.6", "license": "MIT" }, + "node_modules/uniqolor": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uniqolor/-/uniqolor-1.1.0.tgz", + "integrity": "sha512-j2XyokF24fsj+L5u6fbu4rM3RQc6VWJuAngYM2k0ZdG3yiVxt0smLkps2GmQIYqK8VkELGdM9vFU/HfOkK/zoQ==" + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", diff --git a/package.json b/package.json index d528e7095..326fdfab0 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "exifreader": "^4.12.0", "express": "^4.18.2", "gpt3-tokenizer": "^1.1.5", + "ip-matching": "^2.1.2", "ipaddr.js": "^2.0.1", "jimp": "^0.22.7", "jquery": "^3.6.4", @@ -25,6 +26,7 @@ "png-chunks-extract": "^1.0.0", "rimraf": "^3.0.2", "sanitize-filename": "^1.6.3", + "uniqolor": "^1.1.0", "webp-converter": "2.3.2", "ws": "^8.13.0", "yargs": "^17.7.1" @@ -40,7 +42,7 @@ "type": "git", "url": "https://github.com/Cohee1207/SillyTavern.git" }, - "version": "1.5.3", + "version": "1.5.4", "scripts": { "start": "node server.js", "pkg": "pkg ." diff --git a/public/index.html b/public/index.html index fe3b2d347..11662d5a6 100644 --- a/public/index.html +++ b/public/index.html @@ -41,6 +41,7 @@ + @@ -366,6 +367,15 @@ +
+ +
+ Display a breakdown of the tokens used in the request. +
+
Context Size (tokens) @@ -738,7 +748,7 @@
Seed
- +
@@ -961,7 +971,7 @@

API

-
+
+
For privacy reasons, your API key will be hidden after you reload the page.
@@ -1065,24 +1075,31 @@
-
+ @@ -1373,12 +1397,20 @@
-
+
-
+
+
+
+ + +

- World Info + World Selector ? @@ -1440,6 +1472,41 @@

+ +
+
+
+ +
+ +
+ +

+ World Info Editor + ? +

+
+ + + +
+ + +
+
+
+ +
+
+ +
+ + + + +
+ +

Soft Prompt

@@ -1663,6 +1730,10 @@ Auto-scroll Chat + +
+
+ +
+ + +
+

Send on Enter @@ -1680,14 +1759,32 @@

-
-
-
+
+ Auto-swipe +
+
+
+ + +
Minimum generated message length
+ +
Blacklisted words
+
+ +
Blacklisted word count to swipe
+ +
+
+
+

Name

- +
@@ -1822,7 +1919,7 @@
- +
@@ -1875,15 +1972,15 @@
- +
- - + +
-
+
@@ -1925,21 +2022,6 @@
-
-
- Add Members -
-
-
-
-
- - -
-
-
-
-
Current Members @@ -1951,6 +2033,21 @@
+
+
+ Add Members +
+
+
+
+
+ + +
+
+
+
+
@@ -1967,7 +2064,7 @@ - +
@@ -2067,38 +2164,7 @@
-
-
-
-
- -
- -

- World Info Editor - ? -

-
- - -
 
-
- - -
-
-
-
-
- -
- -   - - -
-
@@ -2177,6 +2243,7 @@
+
@@ -2186,14 +2253,14 @@
@@ -2327,6 +2394,8 @@ ${characterName}
+
+
@@ -2415,12 +2484,18 @@
CHAR is typing
+
+
@@ -2430,9 +2505,9 @@
-
+
-
+
@@ -2449,6 +2524,10 @@ Save bookmark + + + Open Author's Note + Convert to group @@ -2487,6 +2566,9 @@
+
+
+
\ No newline at end of file diff --git a/public/notes/content.md b/public/notes/content.md index edbf848d2..f0920c276 100644 --- a/public/notes/content.md +++ b/public/notes/content.md @@ -393,26 +393,9 @@ _Lost API keys can't be restored! Make sure to keep it safe!_ ## Anchors -Anchors are used to increase the length of messages. -There are two types of anchors: _Character Anchor_ and _Style Anchor_. +This feature is considered obsolete and has been removed. -_Character Anchor_ - affects the character played by the AI by motivating it to write longer messages. - -Looks like: `[Elaborate speaker]` - -_Style Anchor_ - affects the entire AI model, motivating the AI to write longer messages even when it is not acting as the character. - -Looks like: `[Writing style: very long messages]` - -*** - -Anchors Order sets the location of anchors in the prompt, the first anchor in the order is much further back in the context and thus has less influence than second. - -The second anchor is only turned on after 8-12 messages, because when the chat still only has a few messages, the first anchor creates enough effect on its own. - -Sometimes an AI model may not perceive anchors correctly or the AI model already generates sufficiently long messages. For these cases, you can disable the anchors by unchecking their respective boxes. - -_When using Pygmalion models these anchors are automatically disabled, since Pygmalion already generates long enough messages._ +The use of the Author's Note extension is now a preferred way to add prompt injections of variable depth. ## Instruct Mode @@ -594,6 +577,8 @@ Characters are drafted based on the order they are presented in group members li ## Multigen +*This feature provides a pseudo-streaming functionality which conflicts with token streaming. When Multigen is enabled and generation API supports streaming, only Multigen streaming will be used.* + SillyTavern tries to create faster and longer responses by chaining the generation using smaller batches. ### Default settings: @@ -614,6 +599,7 @@ Next batches = 30 tokens 2. Character starts speaking for You. 3. <|endoftext|> token reached. 4. No text generated. +5. Stop sequence generated. (Instruct mode only) ## User Settings diff --git a/public/script.js b/public/script.js index dab17418d..fca41f91d 100644 --- a/public/script.js +++ b/public/script.js @@ -106,7 +106,7 @@ import { setPoeOnlineStatus, } from "./scripts/poe.js"; -import { debounce, delay, restoreCaretPosition, saveCaretPosition } from "./scripts/utils.js"; +import { debounce, delay, restoreCaretPosition, saveCaretPosition, end_trim_to_sentence } from "./scripts/utils.js"; import { extension_settings, loadExtensionSettings } from "./scripts/extensions.js"; import { executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "./scripts/slash-commands.js"; import { @@ -125,6 +125,8 @@ import { secret_state, writeSecret } from "./scripts/secrets.js"; +import uniqolor from "./scripts/uniqolor.js"; +import { EventEmitter } from './scripts/eventemitter.js'; //exporting functions and vars for mods export { @@ -203,6 +205,10 @@ hljs.addPlugin({ "before:highlightElement": ({ el }) => { el.textContent = el.in let converter; reloadMarkdownProcessor(); +// array for prompt token calculations +console.log('initializing Prompt Itemization Array on Startup'); +let itemizedPrompts = []; + /* let bg_menu_toggle = false; */ export const systemUserName = "SillyTavern System"; let default_user_name = "You"; @@ -241,12 +247,15 @@ let optionsPopper = Popper.createPopper(document.getElementById('send_form'), do let exportPopper = Popper.createPopper(document.getElementById('export_button'), document.getElementById('export_format_popup'), { placement: 'left' }); +let rawPromptPopper = Popper.createPopper(document.getElementById('dialogue_popup'), document.getElementById('rawPromptPopup'), { + placement: 'right' +}); let dialogueResolve = null; let chat_metadata = {}; let streamingProcessor = null; let crop_data = undefined; - +let is_delete_mode = false; let fav_ch_checked = false; //initialize global var for future cropped blobs @@ -375,6 +384,12 @@ const system_messages = { }, }; +export const event_types = { + EXTRAS_CONNECTED: 'extras_connected', +} + +export const eventSource = new EventEmitter(); + // refresh token $(document).ajaxError(function myErrorHandler(_, xhr) { if (xhr.status == 403) { @@ -403,18 +418,21 @@ async function getClientVersion() { } } -function getTokenCount(str, padding = 0) { +function getTokenCount(str, padding = undefined) { let tokenizerType = power_user.tokenizer; if (main_api === 'openai') { - // For main prompt building - if (padding == power_user.token_padding) { + if (padding === power_user.token_padding) { + // For main "shadow" prompt building tokenizerType = tokenizers.NONE; - // For extensions and WI } else { + // For extensions and WI return getTokenCountOpenAI(str); } + } + if (padding === undefined) { + padding = 0; } switch (tokenizerType) { @@ -528,10 +546,6 @@ var message_already_generated = ""; var cycle_count_generation = 0; var swipes = true; - -let anchor_order = 0; -let style_anchor = true; -let character_anchor = true; let extension_prompts = {}; var main_api;// = "kobold"; @@ -548,6 +562,8 @@ const MAX_GENERATION_LOOPS = 5; let token; +var PromptArrayItemForRawPromptDisplay; + export function getRequestHeaders() { return { "Content-Type": "application/json", @@ -1126,8 +1142,35 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true if (isSystem) { newMessage.find(".mes_edit").hide(); + newMessage.find(".mes_prompt").hide(); //don't need prompt button for sys } + // don't need prompt button for user + if (params.isUser === true) { + newMessage.find(".mes_prompt").hide(); + console.log(`hiding prompt for user mesID ${params.mesId}`); + } + + //shows or hides the Prompt display button + let mesIdToFind = Number(newMessage.attr('mesId')); + if (itemizedPrompts.length !== 0) { + //console.log(`itemizedPrompt.length = ${itemizedPrompts.length}`) + for (var i = 0; i < itemizedPrompts.length; i++) { + if (itemizedPrompts[i].mesId === mesIdToFind) { + newMessage.find(".mes_prompt").show(); + console.log(`showing prompt for mesID ${params.mesId} from ${params.characterName}`); + } else { + console.log(`no cache obj for mesID ${mesIdToFind}, hiding prompt button and continuing search`); + newMessage.find(".mes_prompt").hide(); + console.log(itemizedPrompts); + } + } + } else if (params.isUser !== true) { //hide all when prompt cache is empty + console.log('saw empty prompt cache, hiding all prompt buttons'); + $(".mes_prompt").hide(); + //console.log(itemizedPrompts); + } else { console.log('skipping prompt data for User Message'); } + newMessage.find('.avatar img').on('error', function () { $(this).hide(); $(this).parent().html(`
`); @@ -1195,7 +1238,6 @@ function substituteParams(content, _name1, _name2) { _name1 = _name1 ?? name1; _name2 = _name2 ?? name2; if (!content) { - console.warn("No content on substituteParams") return '' } @@ -1227,15 +1269,16 @@ function getStoppingStrings(isImpersonate, addSpace) { } } + // 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; + if (power_user.instruct.enabled) { - // Cohee: This was borrowed from oobabooga's textgen. But.. - // What if a model doesn't use newlines to chain sequences? - // Who knows. if (power_user.instruct.input_sequence) { - result.push(`\n${power_user.instruct.input_sequence}`); + result.push(wrap(power_user.instruct.input_sequence)); } if (power_user.instruct.output_sequence) { - result.push(`\n${power_user.instruct.output_sequence}`); + result.push(wrap(power_user.instruct.output_sequence)); } } @@ -1448,10 +1491,10 @@ class StreamingProcessor { return text; } - onProgressStreaming(messageId, text) { + onProgressStreaming(messageId, text, isFinal) { const isImpersonate = this.type == "impersonate"; text = this.removePrefix(text); - let processedText = cleanUpMessage(text, isImpersonate); + let processedText = cleanUpMessage(text, isImpersonate, !isFinal); let result = extractNameFromMessage(processedText, this.force_name2, isImpersonate); let isName = result.this_mes_is_name; processedText = result.getMessage; @@ -1488,15 +1531,48 @@ class StreamingProcessor { onFinishStreaming(messageId, text) { this.hideStopButton(this.messageId); - this.onProgressStreaming(messageId, text); + this.onProgressStreaming(messageId, text, true); addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`)); - playMessageSound(); saveChatConditional(); activateSendButtons(); showSwipeButtons(); setGenerationProgress(0); $('.mes_buttons:last').show(); generatedPromtCache = ''; + + console.log("Generated text size:", text.length, text) + + if (power_user.auto_swipe) { + function containsBlacklistedWords(str, blacklist, threshold) { + const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi'); + const matches = str.match(regex) || []; + return matches.length >= threshold; + } + + const generatedTextFiltered = (text) => { + if (text) { + if (power_user.auto_swipe_minimum_length) { + if (text.length < power_user.auto_swipe_minimum_length && text.length !== 0) { + console.log("Generated text size too small") + return true + } + } + if (power_user.auto_swipe_blacklist_threshold) { + if (containsBlacklistedWords(text, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) { + console.log("Generated text has blacklisted words") + return true + } + } + } + return false + } + + if (generatedTextFiltered(text)) { + swipe_right() + return + } + } + playMessageSound(); } onErrorStreaming() { @@ -1575,6 +1651,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, const isImpersonate = type == "impersonate"; const isInstruct = power_user.instruct.enabled; + message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `; // Name for the multigen prefix const magName = isImpersonate ? (is_pygmalion ? 'You' : name1) : name2; @@ -1615,10 +1692,10 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, // Set empty promise resolution functions if (typeof resolve !== 'function') { - resolve = () => {}; + resolve = () => { }; } if (typeof reject !== 'function') { - reject = () => {}; + reject = () => { }; } if (selected_group && !is_group_generating) { @@ -1664,49 +1741,13 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, // bias from the latest message is top priority// promptBias = messageBias ?? promptBias ?? ''; - // Compute anchors - const topAnchorDepth = 8; - const bottomAnchorThreshold = 8; - let anchorTop = ''; - let anchorBottom = ''; - if (!is_pygmalion) { - console.log('saw not pyg'); - - let postAnchorChar = character_anchor ? name2 + " Elaborate speaker" : ""; - let postAnchorStyle = style_anchor ? "Writing style: very long messages" : ""; - if (anchor_order === 0) { - anchorTop = postAnchorChar; - anchorBottom = postAnchorStyle; - } else { // anchor_order === 1 - anchorTop = postAnchorStyle; - anchorBottom = postAnchorChar; - } - - if (anchorBottom) { - anchorBottom = "[" + anchorBottom + "]"; - } - } - //********************************* //PRE FORMATING STRING //********************************* //for normal messages sent from user.. if (textareaText != "" && !automatic_trigger && type !== 'quiet') { - chat[chat.length] = {}; - chat[chat.length - 1]['name'] = name1; - chat[chat.length - 1]['is_user'] = true; - chat[chat.length - 1]['is_name'] = true; - chat[chat.length - 1]['send_date'] = humanizedDateTime(); - chat[chat.length - 1]['mes'] = textareaText; - chat[chat.length - 1]['extra'] = {}; - - if (messageBias) { - console.log('checking bias'); - chat[chat.length - 1]['extra']['bias'] = messageBias; - } - //console.log('Generate calls addOneMessage'); - addOneMessage(chat[chat.length - 1]); + sendMessageAsUser(textareaText, messageBias); } //////////////////////////////////// const scenarioText = chat_metadata['scenario'] || characters[this_chid].scenario; @@ -1742,6 +1783,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, console.log(`Core/all messages: ${coreChat.length}/${chat.length}`); if (main_api === 'openai') { + message_already_generated = ''; // OpenAI doesn't have multigen setOpenAIMessages(coreChat, quiet_prompt); setOpenAIMessageExamples(mesExamplesArray); } @@ -1754,11 +1796,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, storyString += appendToStoryString(Scenario, power_user.disable_scenario_formatting ? '' : 'Scenario: '); } else { storyString += appendToStoryString(charDescription, ''); - - if (coreChat.length < topAnchorDepth) { - storyString += appendToStoryString(charPersonality, power_user.disable_personality_formatting ? '' : name2 + "'s personality: "); - } - + storyString += appendToStoryString(charPersonality, power_user.disable_personality_formatting ? '' : name2 + "'s personality: "); storyString += appendToStoryString(Scenario, power_user.disable_scenario_formatting ? '' : 'Circumstances and context of the dialogue: '); } @@ -1792,7 +1830,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, } else { this_mes_ch_name = charName; } - if (coreChat[j]['is_name']) { + if (coreChat[j]['is_name'] || selected_group) { chat2[i] = this_mes_ch_name + ': ' + coreChat[j]['mes'] + '\n'; } else { chat2[i] = coreChat[j]['mes'] + '\n'; @@ -1808,28 +1846,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, //chat2 = chat2.reverse(); // Determine token limit - let this_max_context = 1487; - if (main_api == 'kobold' || main_api == 'textgenerationwebui') { - this_max_context = (max_context - amount_gen); - } - if (main_api == 'novel') { - if (novel_tier === 1) { - this_max_context = 1024; - } else { - this_max_context = 2048 - 60;//fix for fat tokens - if (nai_settings.model_novel == 'krake-v2') { - this_max_context -= 160; - } - } - } - if (main_api == 'openai') { - this_max_context = oai_settings.openai_max_context; - } - if (main_api == 'poe') { - this_max_context = Number(max_context); - } - - + let this_max_context = getMaxContextSize(); // Adjust token limit for Horde let adjustedParams; @@ -1867,9 +1884,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, storyString, examplesString, chatString, - anchorTop, - anchorBottom, - charPersonality, promptBias, allAnchors, quiet_prompt, @@ -1931,7 +1945,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, generatedPromtCache += cycleGenerationPromt; if (generatedPromtCache.length == 0) { if (main_api === 'openai') { - generateOpenAIPromptCache(charPersonality, topAnchorDepth, anchorTop, bottomAnchorThreshold, anchorBottom); + generateOpenAIPromptCache(); } console.log('generating prompt'); @@ -1954,21 +1968,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, //item = item.substr(0, item.length - 1); } } - if (i === arrMes.length - topAnchorDepth && !is_pygmalion) { - //chatString = chatString.substr(0,chatString.length-1); - //anchorAndPersonality = "[Genre: roleplay chat][Tone: very long messages with descriptions]"; - let personalityAndAnchor = [charPersonality, anchorTop].filter(x => x).join(' '); - if (personalityAndAnchor) { - item += "[" + personalityAndAnchor + "]\n"; - } - } - if (i === arrMes.length - 1 && coreChat.length > bottomAnchorThreshold && item.trim().startsWith(name1 + ":") && !is_pygmalion) {//For add anchor in end - //chatString+=postAnchor+"\n";//"[Writing style: very long messages]\n"; - if (anchorBottom) { - item = item.replace(/\n$/, " "); - item += anchorBottom + "\n"; - } - } if (is_pygmalion && !isInstruct) { if (i === arrMes.length - 1 && item.trim().startsWith(name1 + ":")) {//for add name2 when user sent item = item + name2 + ":"; @@ -2056,9 +2055,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, storyString, mesExmString, mesSendString, - anchorTop, - anchorBottom, - charPersonality, generatedPromtCache, promptBias, allAnchors, @@ -2089,23 +2085,10 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, } // add a custom dingus (if defined) - if (power_user.custom_chat_separator && power_user.custom_chat_separator.length) { - mesSendString = power_user.custom_chat_separator + '\n' + mesSendString; - } - // if chat start formatting is disabled - else if (power_user.disable_start_formatting) { - mesSendString = mesSendString; - } - // add non-pygma dingus - else if (!is_pygmalion) { - mesSendString = '\nThen the roleplay chat between ' + name1 + ' and ' + name2 + ' begins.\n' + mesSendString; - } - // add pygma - else { - mesSendString = '\n' + mesSendString; - //mesSendString = mesSendString; //This edit simply removes the first "" that is prepended to all context prompts - } - let finalPromt = worldInfoBefore + + mesSendString = adjustChatsSeparator(mesSendString); + + let finalPromt = + worldInfoBefore + storyString + worldInfoAfter + afterScenarioAnchor + @@ -2114,70 +2097,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, generatedPromtCache + promptBias; - /* let finalPromptTokens = getTokenCount(finalPromt); - let allAnchorsTokens = getTokenCount(allAnchors); - let afterScenarioAnchorTokens = getTokenCount(afterScenarioAnchor); - let zeroDepthAnchorTokens = getTokenCount(afterScenarioAnchor); - let worldInfoStringTokens = getTokenCount(worldInfoString); - let storyStringTokens = getTokenCount(storyString); - let examplesStringTokens = getTokenCount(examplesString); - let charPersonalityTokens = getTokenCount(charPersonality); - let charDescriptionTokens = getTokenCount(charDescription); - let scenarioTextTokens = getTokenCount(scenarioText); - let promptBiasTokens = getTokenCount(promptBias); - let mesSendStringTokens = getTokenCount(mesSendString) - let ActualChatHistoryTokens = mesSendStringTokens - allAnchorsTokens + power_user.token_padding; - - let totalTokensInPrompt = - allAnchorsTokens + // AN and/or legacy anchors - //afterScenarioAnchorTokens + //only counts if AN is set to 'after scenario' - //zeroDepthAnchorTokens + //same as above, even if AN not on 0 depth - worldInfoStringTokens + - storyStringTokens + //chardefs total - promptBiasTokens + //{{}} - ActualChatHistoryTokens + //chat history - power_user.token_padding; - - console.log( - ` - Prompt Itemization - ------------------- - Extension Add-ins AN: ${allAnchorsTokens} - - World Info: ${worldInfoStringTokens} - - Character Definitions: ${storyStringTokens} - -- Description: ${charDescriptionTokens} - -- Example Messages: ${examplesStringTokens} - -- Character Personality: ${charPersonalityTokens} - -- Character Scenario: ${scenarioTextTokens} - - Chat History: ${ActualChatHistoryTokens} - {{}} Bias: ${promptBiasTokens} - Padding: ${power_user.token_padding} - ------------------- - Total Tokens in Prompt: ${totalTokensInPrompt} - vs - finalPrompt: ${finalPromptTokens} - Max Context: ${this_max_context} - - ` - ); */ - if (zeroDepthAnchor && zeroDepthAnchor.length) { if (!isMultigenEnabled() || tokens_already_generated == 0) { - const trimBothEnds = !force_name2 && !is_pygmalion; - let trimmedPrompt = (trimBothEnds ? zeroDepthAnchor.trim() : zeroDepthAnchor.trimEnd()); - - if (trimBothEnds && !finalPromt.endsWith('\n')) { - finalPromt += '\n'; - } - - finalPromt += trimmedPrompt; - - if (force_name2 || is_pygmalion) { - finalPromt += ' '; - } + finalPromt = appendZeroDepthAnchor(force_name2, zeroDepthAnchor, finalPromt); } } @@ -2191,31 +2113,11 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, if (isMultigenEnabled() && type !== 'quiet') { // if nothing has been generated yet.. - if (tokens_already_generated === 0) { - // if the max gen setting is > 50...( - if (parseInt(amount_gen) >= power_user.multigen_first_chunk) { - // then only try to make 50 this cycle.. - this_amount_gen = power_user.multigen_first_chunk; - } - else { - // otherwise, make as much as the max amount request. - this_amount_gen = parseInt(amount_gen); - } - } - // if we already received some generated text... - else { - // if the remaining tokens to be made is less than next potential cycle count - if (parseInt(amount_gen) - tokens_already_generated < power_user.multigen_next_chunks) { - // subtract already generated amount from the desired max gen amount - this_amount_gen = parseInt(amount_gen) - tokens_already_generated; - } - else { - // otherwise make the standard cycle amount (first 50, and 30 after that) - this_amount_gen = power_user.multigen_next_chunks; - } - } + this_amount_gen = getMultigenAmount(); } + let thisPromptBits = []; + if (main_api == 'kobold' && horde_settings.use_horde && horde_settings.auto_adjust_response_length) { this_amount_gen = Math.min(this_amount_gen, adjustedParams.maxLength); this_amount_gen = Math.max(this_amount_gen, MIN_AMOUNT_GEN); // prevent validation errors @@ -2237,28 +2139,41 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, generate_data = getKoboldGenerationData(finalPromt, this_settings, this_amount_gen, maxContext, isImpersonate); } } - - if (main_api == 'textgenerationwebui') { + else if (main_api == 'textgenerationwebui') { generate_data = getTextGenGenerationData(finalPromt, this_amount_gen, isImpersonate); } - - if (main_api == 'novel') { + else if (main_api == 'novel') { const this_settings = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]]; generate_data = getNovelGenerationData(finalPromt, this_settings); } + else if (main_api == 'openai') { + let [prompt, counts] = await prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldInfoAfter, afterScenarioAnchor, promptBias, type); + generate_data = { prompt: prompt }; + + // counts will return false if the user has not enabled the token breakdown feature + if (counts) { + parseTokenCounts(counts, thisPromptBits); + } + + setInContextMessages(openai_messages_count, type); + } else if (main_api == 'poe') { + generate_data = { prompt: finalPromt }; + } + + if (power_user.console_log_prompts) { + + console.log(generate_data.prompt); + } let generate_url = getGenerateUrl(); console.log('rungenerate calling API'); if (main_api == 'openai') { - let prompt = await prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldInfoAfter, afterScenarioAnchor, promptBias, type); - setInContextMessages(openai_messages_count, type); - if (isStreamingEnabled() && type !== 'quiet') { - streamingProcessor.generator = await sendOpenAIRequest(type, prompt, streamingProcessor.abortController.signal); + streamingProcessor.generator = await sendOpenAIRequest(type, generate_data.prompt, streamingProcessor.abortController.signal); } else { - sendOpenAIRequest(type, prompt).then(onSuccess).catch(onError); + sendOpenAIRequest(type, generate_data.prompt).then(onSuccess).catch(onError); } } else if (main_api == 'kobold' && horde_settings.use_horde) { @@ -2272,7 +2187,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, generatePoe(type, finalPromt).then(onSuccess).catch(onError); } } - else if (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming && type !== 'quiet') { + else if (main_api == 'textgenerationwebui' && isStreamingEnabled() && type !== 'quiet') { streamingProcessor.generator = await generateTextGenWithStreaming(generate_data, streamingProcessor.abortController.signal); } else { @@ -2291,6 +2206,40 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, }); //end of "if not data error" } + //set array object for prompt token itemization of this message + let currentArrayEntry = Number(thisPromptBits.length - 1); + let additionalPromptStuff = { + ...thisPromptBits[currentArrayEntry], + rawPrompt: generate_data.prompt, + mesId: Number(count_view_mes), + worldInfoBefore: worldInfoBefore, + allAnchors: allAnchors, + summarizeString: (extension_prompts['1_memory']?.value || ''), + authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''), + worldInfoString: worldInfoString, + storyString: storyString, + worldInfoAfter: worldInfoAfter, + afterScenarioAnchor: afterScenarioAnchor, + examplesString: examplesString, + mesSendString: mesSendString, + generatedPromtCache: generatedPromtCache, + promptBias: promptBias, + finalPromt: finalPromt, + charDescription: charDescription, + charPersonality: charPersonality, + scenarioText: scenarioText, + this_max_context: this_max_context, + padding: power_user.token_padding, + main_api: main_api, + }; + + thisPromptBits = additionalPromptStuff; + + //console.log(thisPromptBits); + + itemizedPrompts.push(thisPromptBits); + //console.log(`pushed prompt bits to itemizedPrompts array. Length is now: ${itemizedPrompts.length}`); + if (isStreamingEnabled() && type !== 'quiet') { hideSwipeButtons(); let getMessage = await streamingProcessor.generate(); @@ -2328,7 +2277,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, ({ type, getMessage } = saveReply('append', getMessage, this_mes_is_name, title)); } } else { - let chunk = cleanUpMessage(message_already_generated, true); + let chunk = cleanUpMessage(message_already_generated, true, true); let extract = extractNameFromMessage(chunk, force_name2, isImpersonate); $('#send_textarea').val(extract.getMessage).trigger('input'); } @@ -2383,10 +2332,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, // regenerate with character speech reenforced // to make sure we leave on swipe type while also adding the name2 appendage setTimeout(() => { - let newType = type == "swipe" ? "swipe" : "force_name2"; - newType = isImpersonate ? type : newType; - - Generate(newType, { automatic_trigger: false, force_name2: true }); + Generate(type, { automatic_trigger, force_name2: true, resolve, reject, quiet_prompt, force_chid }); }, generate_loop_counter * 1000); } } else { @@ -2429,6 +2375,466 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, //console.log('generate ending'); } //generate ends +function sendMessageAsUser(textareaText, messageBias) { + chat[chat.length] = {}; + chat[chat.length - 1]['name'] = name1; + chat[chat.length - 1]['is_user'] = true; + chat[chat.length - 1]['is_name'] = true; + chat[chat.length - 1]['send_date'] = humanizedDateTime(); + chat[chat.length - 1]['mes'] = textareaText; + chat[chat.length - 1]['extra'] = {}; + + if (messageBias) { + console.log('checking bias'); + chat[chat.length - 1]['extra']['bias'] = messageBias; + } + //console.log('Generate calls addOneMessage'); + addOneMessage(chat[chat.length - 1]); +} + +function getMaxContextSize() { + let this_max_context = 1487; + if (main_api == 'kobold' || main_api == 'textgenerationwebui') { + this_max_context = (max_context - amount_gen); + } + if (main_api == 'novel') { + if (novel_tier === 1) { + this_max_context = 1024; + } else { + this_max_context = 2048 - 60; //fix for fat tokens + if (nai_settings.model_novel == 'krake-v2') { + this_max_context -= 160; + } + } + } + if (main_api == 'openai') { + this_max_context = oai_settings.openai_max_context; + } + if (main_api == 'poe') { + this_max_context = Number(max_context); + } + return this_max_context; +} + +function parseTokenCounts(counts, thisPromptBits) { + const breakdown_bar = $('#token_breakdown div:first-child'); + breakdown_bar.empty(); + + const total = Object.values(counts).filter(x => !Number.isNaN(x)).reduce((acc, val) => acc + val, 0); + + thisPromptBits.push({ + oaiStartTokens: Object.entries(counts)[0][1], + oaiPromptTokens: Object.entries(counts)[1][1], + oaiBiasTokens: Object.entries(counts)[2][1], + oaiNudgeTokens: Object.entries(counts)[3][1], + oaiJailbreakTokens: Object.entries(counts)[4][1], + oaiImpersonateTokens: Object.entries(counts)[5][1], + oaiExamplesTokens: Object.entries(counts)[6][1], + oaiConversationTokens: Object.entries(counts)[7][1], + oaiTotalTokens: total, + }); + + Object.entries(counts).forEach(([type, value]) => { + if (value === 0) { + return; + } + const percent_value = (value / total) * 100; + const color = uniqolor(type, { saturation: 50, lightness: 75, }).color; + const bar = document.createElement('div'); + bar.style.width = `${percent_value}%`; + bar.classList.add('token_breakdown_segment'); + bar.style.backgroundColor = color + 'AA'; + bar.style.borderColor = color + 'FF'; + bar.innerText = value; + bar.title = `${type}: ${percent_value.toFixed(2)}%`; + breakdown_bar.append(bar); + }); +} + +function adjustChatsSeparator(mesSendString) { + if (power_user.custom_chat_separator && power_user.custom_chat_separator.length) { + mesSendString = power_user.custom_chat_separator + '\n' + mesSendString; + } + + // if chat start formatting is disabled + else if (power_user.disable_start_formatting) { + mesSendString = mesSendString; + } + + // add non-pygma dingus + else if (!is_pygmalion) { + mesSendString = '\nThen the roleplay chat between ' + name1 + ' and ' + name2 + ' begins.\n' + mesSendString; + } + + // add pygma + else { + mesSendString = '\n' + mesSendString; + //mesSendString = mesSendString; //This edit simply removes the first "" that is prepended to all context prompts + } + + return mesSendString; +} + +function appendZeroDepthAnchor(force_name2, zeroDepthAnchor, finalPromt) { + const trimBothEnds = !force_name2 && !is_pygmalion; + let trimmedPrompt = (trimBothEnds ? zeroDepthAnchor.trim() : zeroDepthAnchor.trimEnd()); + + if (trimBothEnds && !finalPromt.endsWith('\n')) { + finalPromt += '\n'; + } + + finalPromt += trimmedPrompt; + + if (force_name2 || is_pygmalion) { + finalPromt += ' '; + } + + return finalPromt; +} + +function getMultigenAmount() { + let this_amount_gen = parseInt(amount_gen); + + if (tokens_already_generated === 0) { + // if the max gen setting is > 50...( + if (parseInt(amount_gen) >= power_user.multigen_first_chunk) { + // then only try to make 50 this cycle.. + this_amount_gen = power_user.multigen_first_chunk; + } + else { + // otherwise, make as much as the max amount request. + this_amount_gen = parseInt(amount_gen); + } + } + // if we already received some generated text... + else { + // if the remaining tokens to be made is less than next potential cycle count + if (parseInt(amount_gen) - tokens_already_generated < power_user.multigen_next_chunks) { + // subtract already generated amount from the desired max gen amount + this_amount_gen = parseInt(amount_gen) - tokens_already_generated; + } + else { + // otherwise make the standard cycle amount (first 50, and 30 after that) + this_amount_gen = power_user.multigen_next_chunks; + } + } + return this_amount_gen; +} + +function promptItemize(itemizedPrompts, requestedMesId) { + var incomingMesId = Number(requestedMesId); + console.log(`looking for MesId ${incomingMesId}`); + var thisPromptSet = undefined; + + for (var i = 0; i < itemizedPrompts.length; i++) { + if (itemizedPrompts[i].mesId === incomingMesId) { + thisPromptSet = i; + PromptArrayItemForRawPromptDisplay = Number(i); + } + } + + if (thisPromptSet === undefined) { + console.log(`couldnt find the right mesId. looked for ${incomingMesId}`); + console.log(itemizedPrompts); + return null; + } + + //these happen regardless of API + var charPersonalityTokens = getTokenCount(itemizedPrompts[thisPromptSet].charPersonality); + var charDescriptionTokens = getTokenCount(itemizedPrompts[thisPromptSet].charDescription); + var scenarioTextTokens = getTokenCount(itemizedPrompts[thisPromptSet].scenarioText); + var allAnchorsTokens = getTokenCount(itemizedPrompts[thisPromptSet].allAnchors); + var summarizeStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].summarizeString); + var authorsNoteStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].authorsNoteString); + var afterScenarioAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].afterScenarioAnchor); + var zeroDepthAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].afterScenarioAnchor); + var worldInfoStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].worldInfoString); + var thisPrompt_max_context = itemizedPrompts[thisPromptSet].this_max_context; + var thisPrompt_padding = itemizedPrompts[thisPromptSet].padding; + var promptBiasTokens = getTokenCount(itemizedPrompts[thisPromptSet].promptBias); + var this_main_api = itemizedPrompts[thisPromptSet].main_api; + + if (this_main_api == 'openai') { + //for OAI API + //console.log('-- Counting OAI Tokens'); + + //var finalPromptTokens = itemizedPrompts[thisPromptSet].oaiTotalTokens; + var oaiStartTokens = itemizedPrompts[thisPromptSet].oaiStartTokens; + var oaiPromptTokens = itemizedPrompts[thisPromptSet].oaiPromptTokens; + var ActualChatHistoryTokens = itemizedPrompts[thisPromptSet].oaiConversationTokens; + var examplesStringTokens = itemizedPrompts[thisPromptSet].oaiExamplesTokens; + var oaiBiasTokens = itemizedPrompts[thisPromptSet].oaiBiasTokens; + var oaiJailbreakTokens = itemizedPrompts[thisPromptSet].oaiJailbreakTokens; + var oaiNudgeTokens = itemizedPrompts[thisPromptSet].oaiNudgeTokens; + var oaiImpersonateTokens = itemizedPrompts[thisPromptSet].oaiImpersonateTokens; + var finalPromptTokens = + oaiBiasTokens + + oaiImpersonateTokens + + oaiJailbreakTokens + + oaiNudgeTokens + + oaiPromptTokens + + ActualChatHistoryTokens + + charDescriptionTokens + + charPersonalityTokens + + allAnchorsTokens + + worldInfoStringTokens + + examplesStringTokens; + // OAI doesn't use padding + thisPrompt_padding = 0; + // Max context size - max completion tokens + thisPrompt_max_context = (oai_settings.openai_max_context - oai_settings.openai_max_tokens); + } else { + //for non-OAI APIs + //console.log('-- Counting non-OAI Tokens'); + var finalPromptTokens = getTokenCount(itemizedPrompts[thisPromptSet].finalPromt); + var storyStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].storyString); + var examplesStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].examplesString); + var mesSendStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].mesSendString) + var ActualChatHistoryTokens = mesSendStringTokens - allAnchorsTokens + power_user.token_padding; + + var totalTokensInPrompt = + storyStringTokens + //chardefs total + worldInfoStringTokens + + ActualChatHistoryTokens + //chat history + allAnchorsTokens + // AN and/or legacy anchors + //afterScenarioAnchorTokens + //only counts if AN is set to 'after scenario' + //zeroDepthAnchorTokens + //same as above, even if AN not on 0 depth + promptBiasTokens; //{{}} + //- thisPrompt_padding; //not sure this way of calculating is correct, but the math results in same value as 'finalPromt' + } + + if (this_main_api == 'openai') { + //console.log('-- applying % on OAI tokens'); + var oaiStartTokensPercentage = ((oaiStartTokens / (finalPromptTokens)) * 100).toFixed(2); + var storyStringTokensPercentage = ((oaiPromptTokens / (finalPromptTokens)) * 100).toFixed(2); + var ActualChatHistoryTokensPercentage = ((ActualChatHistoryTokens / (finalPromptTokens)) * 100).toFixed(2); + var promptBiasTokensPercentage = ((oaiBiasTokens / (finalPromptTokens)) * 100).toFixed(2); + var worldInfoStringTokensPercentage = ((worldInfoStringTokens / (finalPromptTokens)) * 100).toFixed(2); + var allAnchorsTokensPercentage = ((allAnchorsTokens / (finalPromptTokens)) * 100).toFixed(2); + var selectedTokenizer = `tiktoken (${oai_settings.openai_model})`; + var oaiSystemTokens = oaiImpersonateTokens + oaiJailbreakTokens + oaiNudgeTokens + oaiStartTokens; + var oaiSystemTokensPercentage = ((oaiSystemTokens / (finalPromptTokens)) * 100).toFixed(2); + + } else { + //console.log('-- applying % on non-OAI tokens'); + var storyStringTokensPercentage = ((storyStringTokens / (totalTokensInPrompt)) * 100).toFixed(2); + var ActualChatHistoryTokensPercentage = ((ActualChatHistoryTokens / (totalTokensInPrompt)) * 100).toFixed(2); + var promptBiasTokensPercentage = ((promptBiasTokens / (totalTokensInPrompt)) * 100).toFixed(2); + var worldInfoStringTokensPercentage = ((worldInfoStringTokens / (totalTokensInPrompt)) * 100).toFixed(2); + var allAnchorsTokensPercentage = ((allAnchorsTokens / (totalTokensInPrompt)) * 100).toFixed(2); + var selectedTokenizer = $("#tokenizer").find(':selected').text(); + } + + if (this_main_api == 'openai') { + //console.log('-- calling popup for OAI tokens'); + callPopup( + ` +

Prompt Itemization

+ Tokenizer: TikToken
+ API Used: ${this_main_api}
+ + Only the white numbers really matter. All numbers are estimates. + Grey color items may not have been included in the context due to certain prompt format settings. + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
System Info:
+
${oaiSystemTokens}
+
+
+
-- Chat Start:
+
${oaiStartTokens}
+
+
+
-- Jailbreak:
+
${oaiJailbreakTokens}
+
+
+
-- NSFW:
+
??
+
+
+
-- Nudge:
+
${oaiNudgeTokens}
+
+
+
-- Impersonate:
+
${oaiImpersonateTokens}
+
+
+
+
+
Prompt Tokens:
+
${oaiPromptTokens}
+
+
+
-- Description:
+
${charDescriptionTokens}
+
+
+
-- Personality:
+
${charPersonalityTokens}
+
+
+
-- Scenario:
+
${scenarioTextTokens}
+
+
+
-- Examples:
+
${examplesStringTokens}
+
+
+
+
World Info:
+
${worldInfoStringTokens}
+
+
+
Chat History:
+
${ActualChatHistoryTokens}
+
+
+
+
Extensions:
+
${allAnchorsTokens}
+
+
+
-- Summarize:
+
${summarizeStringTokens}
+
+
+
-- Author's Note:
+
${authorsNoteStringTokens}
+
+
+
+
{{}} Bias:
${oaiBiasTokens}
+
+
+ +
+
+
+
+
Total Tokens in Prompt:
${finalPromptTokens}
+
+
+
Max Context (Context Size - Response Length):
${thisPrompt_max_context}
+
+
+
+
+ `, 'text' + ); + + } else { + //console.log('-- calling popup for non-OAI tokens'); + callPopup( + ` +

Prompt Itemization

+ Tokenizer: ${selectedTokenizer}
+ API Used: ${this_main_api}
+ + Only the white numbers really matter. All numbers are estimates. + Grey color items may not have been included in the context due to certain prompt format settings. + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
Character Definitions:
+
${storyStringTokens}
+
+
+
-- Description:
+
${charDescriptionTokens}
+
+
+
-- Personality:
+
${charPersonalityTokens}
+
+
+
-- Scenario:
+
${scenarioTextTokens}
+
+
+
-- Examples:
+
${examplesStringTokens}
+
+
+
+
World Info:
+
${worldInfoStringTokens}
+
+
+
Chat History:
+
${ActualChatHistoryTokens}
+
+
+
+
Extensions:
+
${allAnchorsTokens}
+
+
+
-- Summarize:
+
${summarizeStringTokens}
+
+
+
-- Author's Note:
+
${authorsNoteStringTokens}
+
+
+
+
{{}} Bias:
${promptBiasTokens}
+
+
+ +
+
+
+
+
Total Tokens in Prompt:
${totalTokensInPrompt}
+
+ +
+
Max Context:
${thisPrompt_max_context}
+
+
+
- Padding:
${thisPrompt_padding}
+
+
+
Actual Max Context Allowed:
${thisPrompt_max_context - thisPrompt_padding}
+
+
+
+
+ `, 'text' + ); + } +} + function setInContextMessages(lastmsg, type) { $("#chat .mes").removeClass('lastInContext'); @@ -2596,7 +3002,11 @@ function extractMessageFromData(data) { return getMessage; } -function cleanUpMessage(getMessage, isImpersonate) { +function cleanUpMessage(getMessage, isImpersonate, displayIncompleteSentences = false) { + if (!displayIncompleteSentences && power_user.trim_sentences) { + getMessage = end_trim_to_sentence(getMessage, power_user.include_newline); + } + if (power_user.collapse_newlines) { getMessage = collapseNewlines(getMessage); } @@ -3156,8 +3566,10 @@ function changeMainAPI() { // Hide common settings for OpenAI if (selectedVal == "openai") { $("#common-gen-settings-block").css("display", "none"); + //$("#token_breakdown").css("display", "flex"); } else { $("#common-gen-settings-block").css("display", "block"); + //$("#token_breakdown").css("display", "none"); } // Hide amount gen for poe if (selectedVal == "poe") { @@ -3321,30 +3733,15 @@ async function getSettings(type) { `#settings_perset_novel option[value=${novelai_setting_names[nai_settings.preset_settings_novel]}]` ).attr("selected", "true"); - //Load AI model config settings (temp, context length, anchors, and anchor order) + //Load AI model config settings amount_gen = settings.amount_gen; if (settings.max_context !== undefined) max_context = parseInt(settings.max_context); - if (settings.anchor_order !== undefined) - anchor_order = parseInt(settings.anchor_order); - if (settings.style_anchor !== undefined) - style_anchor = !!settings.style_anchor; - if (settings.character_anchor !== undefined) - character_anchor = !!settings.character_anchor; - - $("#style_anchor").prop("checked", style_anchor); - $("#character_anchor").prop("checked", character_anchor); - $("#anchor_order option[value=" + anchor_order + "]").attr( - "selected", - "true" - ); swipes = settings.swipes !== undefined ? !!settings.swipes : true; // enable swipes by default $('#swipes-checkbox').prop('checked', swipes); /// swipecode - //console.log('getSettings -- swipes = ' + swipes + '. toggling box'); hideSwipeButtons(); - //console.log('getsettings calling showswipebtns'); showSwipeButtons(); // Kobold @@ -3442,9 +3839,6 @@ async function saveSettings(type) { user_avatar: user_avatar, amount_gen: amount_gen, max_context: max_context, - anchor_order: anchor_order, - style_anchor: style_anchor, - character_anchor: character_anchor, main_api: main_api, world_info: world_info, world_info_depth: world_info_depth, @@ -4102,6 +4496,8 @@ window["SillyTavern"].getContext = function () { maxContext: Number(max_context), chatMetadata: chat_metadata, streamingProcessor, + eventSource: eventSource, + event_types: event_types, addOneMessage: addOneMessage, generate: Generate, getTokenCount: getTokenCount, @@ -4118,7 +4514,162 @@ window["SillyTavern"].getContext = function () { }; }; +// when we click swipe right button +const swipe_right = () => { + if (chat.length - 1 === Number(this_edit_mes_id)) { + closeMessageEditor(); + } + if (isHordeGenerationNotAllowed()) { + return; + } + + const swipe_duration = 200; + const swipe_range = 700; + //console.log(swipe_range); + let run_generate = false; + let run_swipe_right = false; + if (chat[chat.length - 1]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array + chat[chat.length - 1]['swipe_id'] = 0; // set it to id 0 + chat[chat.length - 1]['swipes'] = []; // empty the array + chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat + } + chat[chat.length - 1]['swipe_id']++; //make new slot in array + // if message has memory attached - remove it to allow regen + if (chat[chat.length - 1].extra && chat[chat.length - 1].extra.memory) { + delete chat[chat.length - 1].extra.memory; + } + //console.log(chat[chat.length-1]['swipes']); + if (parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { //if swipe id of last message is the same as the length of the 'swipes' array + delete chat[chat.length - 1].gen_started; + delete chat[chat.length - 1].gen_finished; + run_generate = true; + } else if (parseInt(chat[chat.length - 1]['swipe_id']) < chat[chat.length - 1]['swipes'].length) { //otherwise, if the id is less than the number of swipes + chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']]; //load the last mes box with the latest generation + run_swipe_right = true; //then prepare to do normal right swipe to show next message + } + + const currentMessage = $("#chat").children().filter(`[mesid="${count_view_mes - 1}"]`); + let this_div = currentMessage.children('.swipe_right'); + let this_mes_div = this_div.parent(); + + if (chat[chat.length - 1]['swipe_id'] > chat[chat.length - 1]['swipes'].length) { //if we swipe right while generating (the swipe ID is greater than what we are viewing now) + chat[chat.length - 1]['swipe_id'] = chat[chat.length - 1]['swipes'].length; //show that message slot (will be '...' while generating) + } + if (run_generate) { //hide swipe arrows while generating + this_div.css('display', 'none'); + } + // handles animated transitions when swipe right, specifically height transitions between messages + if (run_generate || run_swipe_right) { + let this_mes_block = this_mes_div.children('.mes_block').children('.mes_text'); + const this_mes_div_height = this_mes_div[0].scrollHeight; + const this_mes_block_height = this_mes_block[0].scrollHeight; + + this_mes_div.children('.swipe_left').css('display', 'flex'); + this_mes_div.children('.mes_block').transition({ // this moves the div back and forth + x: '-' + swipe_range, + duration: swipe_duration, + easing: animation_easing, + queue: false, + complete: function () { + /*if (!selected_group) { + var typingIndicator = $("#typing_indicator_template .typing_indicator").clone(); + typingIndicator.find(".typing_indicator_name").text(characters[this_chid].name); + } */ + /* $("#chat").append(typingIndicator); */ + const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop("scrollHeight") - $('#chat').outerHeight()) - 10); + //console.log(parseInt(chat[chat.length-1]['swipe_id'])); + //console.log(chat[chat.length-1]['swipes'].length); + if (run_generate && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { + //console.log('showing ""..."'); + /* if (!selected_group) { + } else { */ + $("#chat") + .find('[mesid="' + (count_view_mes - 1) + '"]') + .find('.mes_text') + .html('...'); //shows "..." while generating + $("#chat") + .find('[mesid="' + (count_view_mes - 1) + '"]') + .find('.mes_timer') + .html(''); // resets the timer + /* } */ + } else { + //console.log('showing previously generated swipe candidate, or "..."'); + //console.log('onclick right swipe calling addOneMessage'); + addOneMessage(chat[chat.length - 1], { type: 'swipe' }); + } + let new_height = this_mes_div_height - (this_mes_block_height - this_mes_block[0].scrollHeight); + if (new_height < 103) new_height = 103; + + + this_mes_div.animate({ height: new_height + 'px' }, { + duration: 0, //used to be 100 + queue: false, + progress: function () { + // Scroll the chat down as the message expands + if (is_animation_scroll) $("#chat").scrollTop($("#chat")[0].scrollHeight); + }, + complete: function () { + this_mes_div.css('height', 'auto'); + // Scroll the chat down to the bottom once the animation is complete + if (is_animation_scroll) $("#chat").scrollTop($("#chat")[0].scrollHeight); + } + }); + this_mes_div.children('.mes_block').transition({ + x: swipe_range, + duration: 0, + easing: animation_easing, + queue: false, + complete: function () { + this_mes_div.children('.mes_block').transition({ + x: '0px', + duration: swipe_duration, + easing: animation_easing, + queue: false, + complete: function () { + if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { + console.log('caught here 2'); + is_send_press = true; + $('.mes_buttons:last').hide(); + Generate('swipe'); + } else { + if (parseInt(chat[chat.length - 1]['swipe_id']) !== chat[chat.length - 1]['swipes'].length) { + saveChatConditional(); + } + } + } + }); + } + }); + } + }); + this_mes_div.children('.avatar').transition({ // moves avatar along with swipe + x: '-' + swipe_range, + duration: swipe_duration, + easing: animation_easing, + queue: false, + complete: function () { + this_mes_div.children('.avatar').transition({ + x: swipe_range, + duration: 0, + easing: animation_easing, + queue: false, + complete: function () { + this_mes_div.children('.avatar').transition({ + x: '0px', + duration: swipe_duration, + easing: animation_easing, + queue: false, + complete: function () { + + } + }); + } + }); + } + }); + } +} $(document).ready(function () { @@ -4165,160 +4716,7 @@ $(document).ready(function () { ///// SWIPE BUTTON CLICKS /////// - $(document).on('click', '.swipe_right', function () { //when we click swipe right button - if (chat.length - 1 === Number(this_edit_mes_id)) { - closeMessageEditor(); - } - - if (isHordeGenerationNotAllowed()) { - return; - } - - const swipe_duration = 200; - const swipe_range = 700; - //console.log(swipe_range); - let run_generate = false; - let run_swipe_right = false; - if (chat[chat.length - 1]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array - chat[chat.length - 1]['swipe_id'] = 0; // set it to id 0 - chat[chat.length - 1]['swipes'] = []; // empty the array - chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat - } - chat[chat.length - 1]['swipe_id']++; //make new slot in array - // if message has memory attached - remove it to allow regen - if (chat[chat.length - 1].extra && chat[chat.length - 1].extra.memory) { - delete chat[chat.length - 1].extra.memory; - } - //console.log(chat[chat.length-1]['swipes']); - if (parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { //if swipe id of last message is the same as the length of the 'swipes' array - delete chat[chat.length - 1].gen_started; - delete chat[chat.length - 1].gen_finished; - run_generate = true; - } else if (parseInt(chat[chat.length - 1]['swipe_id']) < chat[chat.length - 1]['swipes'].length) { //otherwise, if the id is less than the number of swipes - chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']]; //load the last mes box with the latest generation - run_swipe_right = true; //then prepare to do normal right swipe to show next message - } - - if (chat[chat.length - 1]['swipe_id'] > chat[chat.length - 1]['swipes'].length) { //if we swipe right while generating (the swipe ID is greater than what we are viewing now) - chat[chat.length - 1]['swipe_id'] = chat[chat.length - 1]['swipes'].length; //show that message slot (will be '...' while generating) - } - if (run_generate) { //hide swipe arrows while generating - $(this).css('display', 'none'); - } - if (run_generate || run_swipe_right) { // handles animated transitions when swipe right, specifically height transitions between messages - - let this_mes_div = $(this).parent(); - let this_mes_block = $(this).parent().children('.mes_block').children('.mes_text'); - const this_mes_div_height = this_mes_div[0].scrollHeight; - const this_mes_block_height = this_mes_block[0].scrollHeight; - - this_mes_div.children('.swipe_left').css('display', 'flex'); - this_mes_div.children('.mes_block').transition({ // this moves the div back and forth - x: '-' + swipe_range, - duration: swipe_duration, - easing: animation_easing, - queue: false, - complete: function () { - /*if (!selected_group) { - var typingIndicator = $("#typing_indicator_template .typing_indicator").clone(); - typingIndicator.find(".typing_indicator_name").text(characters[this_chid].name); - } */ - /* $("#chat").append(typingIndicator); */ - const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop("scrollHeight") - $('#chat').outerHeight()) - 10); - //console.log(parseInt(chat[chat.length-1]['swipe_id'])); - //console.log(chat[chat.length-1]['swipes'].length); - if (run_generate && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { - //console.log('showing ""..."'); - /* if (!selected_group) { - } else { */ - $("#chat") - .find('[mesid="' + (count_view_mes - 1) + '"]') - .find('.mes_text') - .html('...'); //shows "..." while generating - $("#chat") - .find('[mesid="' + (count_view_mes - 1) + '"]') - .find('.mes_timer') - .html(''); // resets the timer - /* } */ - } else { - //console.log('showing previously generated swipe candidate, or "..."'); - //console.log('onclick right swipe calling addOneMessage'); - addOneMessage(chat[chat.length - 1], { type: 'swipe' }); - } - let new_height = this_mes_div_height - (this_mes_block_height - this_mes_block[0].scrollHeight); - if (new_height < 103) new_height = 103; - - - this_mes_div.animate({ height: new_height + 'px' }, { - duration: 0, //used to be 100 - queue: false, - progress: function () { - // Scroll the chat down as the message expands - if (is_animation_scroll) $("#chat").scrollTop($("#chat")[0].scrollHeight); - }, - complete: function () { - this_mes_div.css('height', 'auto'); - // Scroll the chat down to the bottom once the animation is complete - if (is_animation_scroll) $("#chat").scrollTop($("#chat")[0].scrollHeight); - } - }); - this_mes_div.children('.mes_block').transition({ - x: swipe_range, - duration: 0, - easing: animation_easing, - queue: false, - complete: function () { - this_mes_div.children('.mes_block').transition({ - x: '0px', - duration: swipe_duration, - easing: animation_easing, - queue: false, - complete: function () { - if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { - console.log('caught here 2'); - is_send_press = true; - $('.mes_buttons:last').hide(); - Generate('swipe'); - } else { - if (parseInt(chat[chat.length - 1]['swipe_id']) !== chat[chat.length - 1]['swipes'].length) { - saveChatConditional(); - } - } - } - }); - } - }); - } - }); - - $(this).parent().children('.avatar').transition({ // moves avatar aong with swipe - x: '-' + swipe_range, - duration: swipe_duration, - easing: animation_easing, - queue: false, - complete: function () { - $(this).parent().children('.avatar').transition({ - x: swipe_range, - duration: 0, - easing: animation_easing, - queue: false, - complete: function () { - $(this).parent().children('.avatar').transition({ - x: '0px', - duration: swipe_duration, - easing: animation_easing, - queue: false, - complete: function () { - - } - }); - } - }); - } - }); - } - - }); + $(document).on('click', '.swipe_right', swipe_right); $(document).on('click', '.swipe_left', function () { // when we swipe left..but no generation. if (chat.length - 1 === Number(this_edit_mes_id)) { @@ -4512,14 +4910,19 @@ $(document).ready(function () { is_use_scroll_holder = false; } }); - $(document).on("click", ".del_checkbox", function () { - //when a 'delete message' checkbox is clicked - $(".del_checkbox").each(function () { + + $(document).on("click", ".mes", function () { + //when a 'delete message' parent div is clicked + // and we are in delete mode and del_checkbox is visible + if (!is_delete_mode || !$(this).children('.del_checkbox').is(':visible')) { + return; + } + $(".mes").children(".del_checkbox").each(function () { $(this).prop("checked", false); $(this).parent().css("background", css_mes_bg); }); - $(this).parent().css("background", "#600"); //sets the bg of the mes selected for deletion - var i = $(this).parent().attr("mesid"); //checks the message ID in the chat + $(this).css("background", "#600"); //sets the bg of the mes selected for deletion + var i = $(this).attr("mesid"); //checks the message ID in the chat this_del_mes = i; while (i < chat.length) { //as long as the current message ID is less than the total chat length @@ -4531,6 +4934,7 @@ $(document).ready(function () { //console.log(i); } }); + $(document).on("click", "#user_avatar_block .avatar", function () { user_avatar = $(this).attr("imgfile"); reloadUserAvatar(); @@ -4781,6 +5185,9 @@ $(document).ready(function () { } } + rawPromptPopper.update(); + $('#rawPromptPopup').hide(); + if (dialogueResolve) { if (popup_type == 'input') { dialogueResolve($("#dialogue_popup_input").val()); @@ -5171,6 +5578,27 @@ $(document).ready(function () { $("#options [id]").on("click", function () { var id = $(this).attr("id"); + + if (id == "option_toggle_AN") { + if (selected_group || this_chid !== undefined && $("#floatingPrompt").css("display") === 'none') { + $("#floatingPrompt").css("display", "flex"); + $("#floatingPrompt").css("opacity", 0.0); + $("#floatingPrompt").transition({ + opacity: 1.0, + duration: animation_duration, + easing: animation_easing, + }); + $("#ANBlockToggle").click(); + } else { + $("#floatingPrompt").transition({ + opacity: 0.0, + duration: 250, + easing: animation_easing, + }); + setTimeout(function () { $("#floatingPrompt").hide(); }, 250); + } + } + if (id == "option_select_chat") { if ((selected_group && !is_group_generating) || (this_chid !== undefined && !is_send_press)) { displayPastChats(); @@ -5225,6 +5653,7 @@ $(document).ready(function () { } }); } + is_delete_mode = true; } }); @@ -5243,6 +5672,7 @@ $(document).ready(function () { this_del_mes = 0; console.log('canceled del msgs, calling showswipesbtns'); showSwipeButtons(); + is_delete_mode = false; }); //confirms message delation with the "ok" button @@ -5271,6 +5701,7 @@ $(document).ready(function () { $('#chat .mes').eq(-2).removeClass('last_mes'); console.log('confirmed del msgs, calling showswipesbtns'); showSwipeButtons(); + is_delete_mode = false; }); $("#settings_perset").change(function () { @@ -5381,38 +5812,6 @@ $(document).ready(function () { ////////////////////////////////////////////////////////////// - - $("#style_anchor").change(function () { - style_anchor = !!$("#style_anchor").prop("checked"); - saveSettingsDebounced(); - }); - - $("#character_anchor").change(function () { - character_anchor = !!$("#character_anchor").prop("checked"); - saveSettingsDebounced(); - }); - - /* $("#donation").click(function () { - $("#shadow_tips_popup").css("display", "block"); - $("#shadow_tips_popup").transition({ - opacity: 1.0, - duration: 100, - easing: animation_easing, - complete: function () { }, - }); - }); */ - - /* $("#tips_cross").click(function () { - $("#shadow_tips_popup").transition({ - opacity: 0.0, - duration: 100, - easing: animation_easing, - complete: function () { - $("#shadow_tips_popup").css("display", "none"); - }, - }); - }); */ - $("#select_chat_cross").click(function () { $("#shadow_select_chat_popup").transition({ opacity: 0, @@ -5456,6 +5855,30 @@ $(document).ready(function () { }); } + $(document).on("pointerup", ".mes_prompt", function () { + let mesIdForItemization = $(this).closest('.mes').attr('mesId'); + if (itemizedPrompts.length !== undefined && itemizedPrompts.length !== 0) { + promptItemize(itemizedPrompts, mesIdForItemization); + } + }) + + $(document).on("pointerup", "#showRawPrompt", function () { + //console.log(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt); + console.log(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt); + + let rawPrompt = itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt; + let rawPromptValues = rawPrompt; + + if (Array.isArray(rawPrompt)) { + rawPromptValues = rawPrompt.map(x => x.content).join('\n'); + } + + //let DisplayStringifiedPrompt = JSON.stringify(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt).replace(/\n+/g, '
'); + $("#rawPromptWrapper").text(rawPromptValues); + rawPromptPopper.update(); + $('#rawPromptPopup').toggle(); + }) + //******************** //***Message Editor*** @@ -5685,11 +6108,6 @@ $(document).ready(function () { is_api_button_press_novel = true; }); - $("#anchor_order").change(function () { - anchor_order = parseInt($("#anchor_order").find(":selected").val()); - saveSettingsDebounced(); - }); - //**************************CHARACTER IMPORT EXPORT*************************// $("#character_import_button").click(function () { $("#character_import_file").click(); @@ -5924,6 +6342,7 @@ $(document).ready(function () { '#shadow_popup', '#world_popup', '.ui-widget', + '.text_pole', ]; for (const id of forbiddenTargets) { if (clickTarget.closest(id).length > 0) { @@ -5945,7 +6364,10 @@ $(document).ready(function () { } }); - $(document).on('click', '.inline-drawer-toggle', function () { + $(document).on('click', '.inline-drawer-toggle', function (e) { + if ($(e.target).hasClass('text_pole')) { + return; + }; var icon = $(this).find('.inline-drawer-icon'); icon.toggleClass('down up'); icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up'); diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js index b17729753..651629bcf 100644 --- a/public/scripts/RossAscends-mods.js +++ b/public/scripts/RossAscends-mods.js @@ -30,11 +30,16 @@ import { import { sortByCssOrder } from "./utils.js"; var NavToggle = document.getElementById("nav-toggle"); + var RPanelPin = document.getElementById("rm_button_panel_pin"); var LPanelPin = document.getElementById("lm_button_panel_pin"); -var SelectedCharacterTab = document.getElementById("rm_button_selected_ch"); +var WIPanelPin = document.getElementById("WI_panel_pin"); + var RightNavPanel = document.getElementById("right-nav-panel"); -var LeftNavPanel = document.getElementById("left-nav-panel") +var LeftNavPanel = document.getElementById("left-nav-panel"); +var WorldInfo = document.getElementById("WorldInfo"); + +var SelectedCharacterTab = document.getElementById("rm_button_selected_ch"); var AdvancedCharDefsPopup = document.getElementById("character_popup"); var ConfirmationPopup = document.getElementById("dialogue_popup"); var AutoConnectCheckbox = document.getElementById("auto-connect-checkbox"); @@ -101,10 +106,20 @@ function waitForElement(querySelector, timeout) { waitForElement("#expression-image", 10000).then(function () { dragElement(document.getElementById("expression-holder")); + dragElement(document.getElementById("floatingPrompt")); + }).catch(() => { console.log("expression holder not loaded yet"); }); +waitForElement("#floatingPrompt", 10000).then(function () { + + dragElement(document.getElementById("floatingPrompt")); + +}).catch(() => { + console.log("floating prompt box not loaded yet"); +}); + // Device detection const deviceInfo = await getDeviceInfo(); @@ -412,23 +427,18 @@ function OpenNavPanels() { if (LoadLocalBool("NavLockOn") == true && LoadLocalBool("NavOpened") == true) { //console.log("RA -- clicking right nav to open"); $("#rightNavDrawerIcon").click(); - } else { - /* console.log('didnt see reason to open right nav on load: R-nav locked? ' + - LoadLocalBool("NavLockOn") - + ' R-nav was open before? ' + - LoadLocalBool("NavOpened" == true)); */ } //auto-open L nav if locked and previously open - if (LoadLocalBool("LNavLockOn") == true && LoadLocalBool("LNavOpened") == true) { console.log("RA -- clicking left nav to open"); $("#leftNavDrawerIcon").click(); - } else { - /* console.log('didnt see reason to open left nav on load: L-Nav Locked? ' + - LoadLocalBool("LNavLockOn") - + ' L-nav was open before? ' + - LoadLocalBool("LNavOpened" == true)); */ + } + + //auto-open WI if locked and previously open + if (LoadLocalBool("WINavLockOn") == true && LoadLocalBool("WINavOpened") == true) { + console.log("RA -- clicking WI to open"); + $("#WIDrawerIcon").click(); } } @@ -438,10 +448,12 @@ dragElement(document.getElementById("sheld")); dragElement(document.getElementById("left-nav-panel")); dragElement(document.getElementById("right-nav-panel")); dragElement(document.getElementById("avatar_zoom_popup")); +dragElement(document.getElementById("WorldInfo")); function dragElement(elmnt) { + var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; if (document.getElementById(elmnt.id + "header")) { //ex: id="sheldheader" // if present, the header is where you move the DIV from, but this overrides everything else: @@ -452,6 +464,7 @@ function dragElement(elmnt) { } function dragMouseDown(e) { + //console.log(e); e = e || window.event; e.preventDefault(); // get the mouse cursor position at startup: @@ -546,6 +559,7 @@ function dragElement(elmnt) { elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; $(elmnt).css("bottom", "unset"); $(elmnt).css("right", "unset"); + $(elmnt).css("margin", "unset"); /* console.log(` offsetLeft: ${elmnt.offsetLeft}, offsetTop: ${elmnt.offsetTop} @@ -614,7 +628,7 @@ $("document").ready(function () { if ($(RightNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) { $(RightNavPanel).slideToggle(200, "swing"); - $(rightNavDrawerIcon).toggleClass('openIcon closedIcon'); + //$(rightNavDrawerIcon).toggleClass('openIcon closedIcon'); $(RightNavPanel).toggleClass('openDrawer closedDrawer'); } } @@ -630,12 +644,30 @@ $("document").ready(function () { if ($(LeftNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) { $(LeftNavPanel).slideToggle(200, "swing"); - $(leftNavDrawerIcon).toggleClass('openIcon closedIcon'); + //$(leftNavDrawerIcon).toggleClass('openIcon closedIcon'); $(LeftNavPanel).toggleClass('openDrawer closedDrawer'); } } }); + $(WIPanelPin).on("click", function () { + SaveLocal("WINavLockOn", $(WIPanelPin).prop("checked")); + if ($(WIPanelPin).prop("checked") == true) { + console.log('adding pin class to WI'); + $(WorldInfo).addClass('pinnedOpen'); + } else { + console.log('removing pin class from WI'); + $(WorldInfo).removeClass('pinnedOpen'); + + if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) { + console.log('closing WI after lock removal'); + $(WorldInfo).slideToggle(200, "swing"); + //$(WorldInfoDrawerIcon).toggleClass('openIcon closedIcon'); + $(WorldInfo).toggleClass('openDrawer closedDrawer'); + } + } + }); + // read the state of right Nav Lock and apply to rightnav classlist $(RPanelPin).prop('checked', LoadLocalBool("NavLockOn")); if (LoadLocalBool("NavLockOn") == true) { @@ -657,6 +689,18 @@ $("document").ready(function () { $(LeftNavPanel).addClass('pinnedOpen'); } + // read the state of left Nav Lock and apply to leftnav classlist + $(WIPanelPin).prop('checked', LoadLocalBool("WINavLockOn")); + if (LoadLocalBool("WINavLockOn") == true) { + //console.log('setting pin class via local var'); + $(WorldInfo).addClass('pinnedOpen'); + } + + if ($(WIPanelPin).prop('checked' == true)) { + console.log('setting pin class via checkbox state'); + $(WorldInfo).addClass('pinnedOpen'); + } + //save state of Right nav being open or closed $("#rightNavDrawerIcon").on("click", function () { if (!$("#rightNavDrawerIcon").hasClass('openIcon')) { @@ -671,6 +715,13 @@ $("document").ready(function () { } else { SaveLocal('LNavOpened', 'false'); } }); + //save state of Left nav being open or closed + $("#WorldInfo").on("click", function () { + if (!$("#WorldInfo").hasClass('openIcon')) { + SaveLocal('WINavOpened', 'true'); + } else { SaveLocal('WINavOpened', 'false'); } + }); + var chatbarInFocus = false; $('#send_textarea').focus(function () { chatbarInFocus = true; diff --git a/public/scripts/eventemitter.js b/public/scripts/eventemitter.js new file mode 100644 index 000000000..eacf7a23b --- /dev/null +++ b/public/scripts/eventemitter.js @@ -0,0 +1,71 @@ +/* Polyfill indexOf. */ +var indexOf; + +if (typeof Array.prototype.indexOf === 'function') { + indexOf = function (haystack, needle) { + return haystack.indexOf(needle); + }; +} else { + indexOf = function (haystack, needle) { + var i = 0, length = haystack.length, idx = -1, found = false; + + while (i < length && !found) { + if (haystack[i] === needle) { + idx = i; + found = true; + } + + i++; + } + + return idx; + }; +}; + + +/* Polyfill EventEmitter. */ +var EventEmitter = function () { + this.events = {}; +}; + +EventEmitter.prototype.on = function (event, listener) { + if (typeof this.events[event] !== 'object') { + this.events[event] = []; + } + + this.events[event].push(listener); +}; + +EventEmitter.prototype.removeListener = function (event, listener) { + var idx; + + if (typeof this.events[event] === 'object') { + idx = indexOf(this.events[event], listener); + + if (idx > -1) { + this.events[event].splice(idx, 1); + } + } +}; + +EventEmitter.prototype.emit = function (event) { + var i, listeners, length, args = [].slice.call(arguments, 1); + + if (typeof this.events[event] === 'object') { + listeners = this.events[event].slice(); + length = listeners.length; + + for (i = 0; i < length; i++) { + listeners[i].apply(this, args); + } + } +}; + +EventEmitter.prototype.once = function (event, listener) { + this.on(event, function g () { + this.removeListener(event, g); + listener.apply(this, arguments); + }); +}; + +export { EventEmitter } \ No newline at end of file diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index 884e96968..49b834539 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -1,4 +1,4 @@ -import { callPopup, saveSettings, saveSettingsDebounced } from "../script.js"; +import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced } from "../script.js"; import { isSubsetOf } from "./utils.js"; export { getContext, @@ -162,6 +162,7 @@ async function connectToApi(baseUrl) { const data = await getExtensionsResult.json(); modules = data.modules; await activateExtensions(); + eventSource.emit(event_types.EXTRAS_CONNECTED, modules); } updateStatus(getExtensionsResult.ok); diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js index 67d3a527a..992413213 100644 --- a/public/scripts/extensions/caption/index.js +++ b/public/scripts/extensions/caption/index.js @@ -96,6 +96,7 @@ $(document).ready(function () { function addSendPictureButton() { const sendButton = document.createElement('div'); sendButton.id = 'send_picture'; + sendButton.title = 'Send a picture to chat'; sendButton.classList.add('fa-solid'); $(sendButton).hide(); $(sendButton).on('click', () => $('#img_file').click()); diff --git a/public/scripts/extensions/dice/index.js b/public/scripts/extensions/dice/index.js index 851b229ba..35f791140 100644 --- a/public/scripts/extensions/dice/index.js +++ b/public/scripts/extensions/dice/index.js @@ -29,7 +29,7 @@ async function doDiceRoll() { function addDiceRollButton() { const buttonHtml = ` -
+
`; const dropdownHtml = `
diff --git a/public/scripts/extensions/floating-prompt/index.js b/public/scripts/extensions/floating-prompt/index.js index ead3cbd1a..0ebb432c1 100644 --- a/public/scripts/extensions/floating-prompt/index.js +++ b/public/scripts/extensions/floating-prompt/index.js @@ -1,5 +1,6 @@ import { chat_metadata, saveSettingsDebounced } from "../../../script.js"; import { extension_settings, getContext } from "../../extensions.js"; +import { registerSlashCommand } from "../../slash-commands.js"; import { debounce } from "../../utils.js"; export { MODULE_NAME }; @@ -19,6 +20,10 @@ const metadata_keys = { position: 'note_position', } +function setNoteCommand(_, text) { + $('#extension_floating_prompt').val(text).trigger('input'); +} + async function onExtensionFloatingPromptInput() { chat_metadata[metadata_keys.prompt] = $(this).val(); saveMetadataDebounced(); @@ -100,7 +105,7 @@ async function moduleWorker() { if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) { context.setExtensionPrompt(MODULE_NAME, ''); - $('#extension_floating_counter').text('No'); + $('#extension_floating_counter').text('(disabled)'); return; } @@ -110,57 +115,67 @@ async function moduleWorker() { const shouldAddPrompt = messagesTillInsertion == 0; const prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : ''; context.setExtensionPrompt(MODULE_NAME, prompt, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]); - $('#extension_floating_counter').text(shouldAddPrompt ? 'This' : messagesTillInsertion); + $('#extension_floating_counter').text(shouldAddPrompt ? '0' : messagesTillInsertion); } (function () { function addExtensionsSettings() { const settingsHtml = ` -
-
-
- Author's Note / Character Bias -
-
-
- - Your notes are saved PER CHAT. When you start a new chat, you'll see the default / empty note.
- Saving a bookmark will copy your note to a bookmark chat. Making changes to it won't update the note in a parent chat.
-
- - -
- - -
- - - - - Appending to the prompt in next: No message(s) -
-
-
-
- Default note for new chats -
+
+
+
+
+
+ Author's Note +
- - + + Unique to this chat.
+ Bookmarks inherit the Note from their parent, and can be changed individually after that.
+
+ + + +
+ + +
+ + + + + (0 = Disable) +
+ + User inputs until next insertion: (disabled) + +
+
+
+
+
+ Default Author's Note +
+
+
+ Will be automatically added as the Author's Note for all new chats. + + +
`; - $('#extensions_settings').append(settingsHtml); + $('#movingDivs').append(settingsHtml); $('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput); $('#extension_floating_interval').on('input', onExtensionFloatingIntervalInput); $('#extension_floating_depth').on('input', onExtensionFloatingDepthInput); @@ -170,4 +185,5 @@ async function moduleWorker() { addExtensionsSettings(); setInterval(moduleWorkerWrapper, UPDATE_INTERVAL); + registerSlashCommand('note', setNoteCommand, [], " – sets an author's note for the currently selected chat", true, true); })(); \ No newline at end of file diff --git a/public/scripts/extensions/floating-prompt/style.css b/public/scripts/extensions/floating-prompt/style.css index 2537e0487..6844684fa 100644 --- a/public/scripts/extensions/floating-prompt/style.css +++ b/public/scripts/extensions/floating-prompt/style.css @@ -1,4 +1,26 @@ -.floating_prompt_settings { +#floatingPrompt { + overflow-y: auto; + max-width: 90svw; + max-height: 90svh; + min-width: 100px; + min-height: 100px; + border-radius: 10px; + border: 1px solid var(--white30a); + position: fixed; + padding: 10px; + display: none; + flex-direction: column; + box-shadow: 0 0 10px var(--black70a); + z-index: 3000; + left: 0; + top: 0; + margin: 0; + right: unset; + width: calc(((100svw - var(--sheldWidth)) / 2) - 1px); + +} + +.floating_prompt_radio_group { display: flex; flex-direction: column; } @@ -11,9 +33,4 @@ .floating_prompt_settings textarea { font-size: calc(var(--mainFontSize) * 0.9); line-height: 1.2; -} - -.floating_prompt_radio_group { - display: flex; - flex-direction: column; } \ No newline at end of file diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index 0d3b5a953..b658dca06 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -3,9 +3,13 @@ import { saveSettingsDebounced, systemUserName, hideSwipeButtons, - showSwipeButtons + showSwipeButtons, + callPopup, + getRequestHeaders, + event_types, + eventSource } from "../../../script.js"; -import { getApiUrl, getContext, extension_settings, defaultRequestArgs } from "../../extensions.js"; +import { getApiUrl, getContext, extension_settings, defaultRequestArgs, modules } from "../../extensions.js"; import { stringFormat, initScrollHeight, resetScrollHeight } from "../../utils.js"; export { MODULE_NAME }; @@ -94,6 +98,9 @@ const defaultSettings = { negative_prompt: 'lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry', sampler: 'DDIM', model: '', + + horde: false, + horde_nsfw: false, } async function loadSettings() { @@ -107,11 +114,10 @@ async function loadSettings() { $('#sd_negative_prompt').val(extension_settings.sd.negative_prompt).trigger('input'); $('#sd_width').val(extension_settings.sd.width).trigger('input'); $('#sd_height').val(extension_settings.sd.height).trigger('input'); - + $('#sd_horde').prop('checked', extension_settings.sd.horde); + $('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw); await Promise.all([loadSamplers(), loadModels()]); - - } function onScaleInput() { @@ -155,10 +161,29 @@ function onHeightInput() { saveSettingsDebounced(); } +async function onHordeInput() { + extension_settings.sd.model = null; + extension_settings.sd.sampler = null; + extension_settings.sd.horde = !!$(this).prop('checked'); + saveSettingsDebounced(); + await Promise.all([loadModels(), loadSamplers()]); +} + +async function onHordeNsfwInput() { + extension_settings.sd.horde_nsfw = !!$(this).prop('checked'); + saveSettingsDebounced(); +} + async function onModelChange() { extension_settings.sd.model = $('#sd_model').find(':selected').val(); saveSettingsDebounced(); + if (extension_settings.sd.horde == false) { + await updateExtrasRemoteModel(); + } +} + +async function updateExtrasRemoteModel() { const url = new URL(getApiUrl()); url.pathname = '/api/image/model'; const getCurrentModelResult = await fetch(url, { @@ -173,25 +198,94 @@ async function onModelChange() { } async function loadSamplers() { + $('#sd_sampler').empty(); + let samplers = []; + + if (extension_settings.sd.horde) { + samplers = await loadHordeSamplers(); + } else { + samplers = await loadExtrasSamplers(); + } + + for (const sampler of samplers) { + const option = document.createElement('option'); + option.innerText = sampler; + option.value = sampler; + option.selected = sampler === extension_settings.sd.sampler; + $('#sd_sampler').append(option); + } +} + +async function loadHordeSamplers() { + const result = await fetch('/horde_samplers', { + method: 'POST', + headers: getRequestHeaders(), + }); + + if (result.ok) { + const data = await result.json(); + return data; + } + + return []; +} + +async function loadExtrasSamplers() { + if (!modules.includes('sd')) { + return []; + } + const url = new URL(getApiUrl()); url.pathname = '/api/image/samplers'; const result = await fetch(url, defaultRequestArgs); if (result.ok) { const data = await result.json(); - const samplers = data.samplers; - - for (const sampler of samplers) { - const option = document.createElement('option'); - option.innerText = sampler; - option.value = sampler; - option.selected = sampler === extension_settings.sd.sampler; - $('#sd_sampler').append(option); - } + return data.samplers; } + + return []; } async function loadModels() { + $('#sd_model').empty(); + let models = []; + + if (extension_settings.sd.horde) { + models = await loadHordeModels(); + } else { + models = await loadExtrasModels(); + } + + for (const model of models) { + const option = document.createElement('option'); + option.innerText = model.text; + option.value = model.value; + option.selected = model.value === extension_settings.sd.model; + $('#sd_model').append(option); + } +} + +async function loadHordeModels() { + const result = await fetch('/horde_models', { + method: 'POST', + headers: getRequestHeaders(), + }); + + if (result.ok) { + const data = await result.json(); + const models = data.map(x => ({ value: x.name, text: `${x.name} (ETA: ${x.eta}s, Queue: ${x.queued}, Workers: ${x.count})` })); + return models; + } + + return []; +} + +async function loadExtrasModels() { + if (!modules.includes('sd')) { + return []; + } + const url = new URL(getApiUrl()); url.pathname = '/api/image/model'; const getCurrentModelResult = await fetch(url, defaultRequestArgs); @@ -206,16 +300,11 @@ async function loadModels() { if (getModelsResult.ok) { const data = await getModelsResult.json(); - const models = data.models; - - for (const model of models) { - const option = document.createElement('option'); - option.innerText = model; - option.value = model; - option.selected = model === extension_settings.sd.model; - $('#sd_model').append(option); - } + const view_models = data.models.map(x => ({ value: x, text: x })); + return view_models; } + + return []; } function getGenerationType(prompt) { @@ -257,6 +346,14 @@ async function generatePicture(_, trigger) { return; } + if (!modules.includes('sd') && !extension_settings.sd.horde) { + callPopup("Extensions API is not connected or doesn't provide SD module. Enable Stable Horde to generate images.", 'text'); + return; + } + + extension_settings.sd.sampler = $('#sd_sampler').find(':selected').val(); + extension_settings.sd.model = $('#sd_model').find(':selected').val(); + trigger = trigger.trim(); const generationMode = getGenerationType(trigger); console.log('Generation mode', generationMode, 'triggered with', trigger); @@ -279,30 +376,10 @@ async function generatePicture(_, trigger) { console.log('Processed Stable Diffusion prompt:', prompt); - const url = new URL(getApiUrl()); - url.pathname = '/api/image'; - const result = await fetch(url, { - method: 'POST', - headers: postHeaders, - body: JSON.stringify({ - prompt: prompt, - sampler: extension_settings.sd.sampler, - steps: extension_settings.sd.steps, - scale: extension_settings.sd.scale, - width: extension_settings.sd.width, - height: extension_settings.sd.height, - prompt_prefix: extension_settings.sd.prompt_prefix, - negative_prompt: extension_settings.sd.negative_prompt, - restore_faces: true, - face_restoration_model: 'GFPGAN', - - }), - }); - - if (result.ok) { - const data = await result.json(); - const base64Image = `data:image/jpeg;base64,${data.image}`; - sendMessage(prompt, base64Image); + if (extension_settings.sd.horde) { + await generateHordeImage(prompt); + } else { + await generateExtrasImage(prompt); } } catch (err) { console.trace(err); @@ -314,6 +391,62 @@ async function generatePicture(_, trigger) { } } +async function generateExtrasImage(prompt) { + const url = new URL(getApiUrl()); + url.pathname = '/api/image'; + const result = await fetch(url, { + method: 'POST', + headers: postHeaders, + body: JSON.stringify({ + prompt: prompt, + sampler: extension_settings.sd.sampler, + steps: extension_settings.sd.steps, + scale: extension_settings.sd.scale, + width: extension_settings.sd.width, + height: extension_settings.sd.height, + prompt_prefix: extension_settings.sd.prompt_prefix, + negative_prompt: extension_settings.sd.negative_prompt, + restore_faces: true, + face_restoration_model: 'GFPGAN', + }), + }); + + if (result.ok) { + const data = await result.json(); + const base64Image = `data:image/jpeg;base64,${data.image}`; + sendMessage(prompt, base64Image); + } else { + callPopup('Image generation has failed. Please try again.', 'text'); + } +} + +async function generateHordeImage(prompt) { + const result = await fetch('/horde_generateimage', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ + prompt: prompt, + sampler: extension_settings.sd.sampler, + steps: extension_settings.sd.steps, + scale: extension_settings.sd.scale, + width: extension_settings.sd.width, + height: extension_settings.sd.height, + prompt_prefix: extension_settings.sd.prompt_prefix, + negative_prompt: extension_settings.sd.negative_prompt, + model: extension_settings.sd.model, + nsfw: extension_settings.sd.horde_nsfw, + }), + }); + + if (result.ok) { + const data = await result.text(); + const base64Image = `data:image/webp;base64,${data}`; + sendMessage(prompt, base64Image); + } else { + callPopup('Image generation has failed. Please try again.', 'text'); + } +} + async function sendMessage(prompt, image) { const context = getContext(); const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`; @@ -336,7 +469,7 @@ async function sendMessage(prompt, image) { function addSDGenButtons() { const buttonHtml = ` -
+
`; const waitButtonHtml = ` @@ -386,16 +519,6 @@ function addSDGenButtons() { async function moduleWorker() { const context = getContext(); - /* if (context.onlineStatus === 'no_connection') { - $('#sd_gen').hide(200); - } else if ($("#send_but").css('display') === 'flex') { - $('#sd_gen').show(200); - $("#sd_gen_wait").hide(200); - } else { - $('#sd_gen').hide(200); - $("#sd_gen_wait").show(200); - } */ - context.onlineStatus === 'no_connection' ? $('#sd_gen').hide(200) : $('#sd_gen').show(200) @@ -444,6 +567,16 @@ jQuery(async () => {
Use slash commands to generate images. Type /help in chat for more details +
+ Hint: Save an API key in Horde KoboldAI API settings to use it here. + + @@ -472,12 +605,17 @@ jQuery(async () => { $('#sd_negative_prompt').on('input', onNegativePromptInput); $('#sd_width').on('input', onWidthInput); $('#sd_height').on('input', onHeightInput); + $('#sd_horde').on('input', onHordeInput); + $('#sd_horde_nsfw').on('input', onHordeNsfwInput); $('.sd_settings .inline-drawer-toggle').on('click', function () { initScrollHeight($("#sd_prompt_prefix")); initScrollHeight($("#sd_negative_prompt")); }) - await loadSettings(); + eventSource.on(event_types.EXTRAS_CONNECTED, async () => { + await Promise.all([loadSamplers(), loadModels()]); + }); + await loadSettings(); }); \ No newline at end of file diff --git a/public/scripts/extensions/stable-diffusion/manifest.json b/public/scripts/extensions/stable-diffusion/manifest.json index 13f8db021..37b437638 100644 --- a/public/scripts/extensions/stable-diffusion/manifest.json +++ b/public/scripts/extensions/stable-diffusion/manifest.json @@ -1,10 +1,10 @@ { "display_name": "Stable Diffusion", "loading_order": 10, - "requires": [ + "requires": [], + "optional": [ "sd" ], - "optional": [], "js": "index.js", "css": "style.css", "author": "Cohee#1207", diff --git a/public/scripts/extensions/stable-diffusion/style.css b/public/scripts/extensions/stable-diffusion/style.css index 5d26a2ad3..3cb72e138 100644 --- a/public/scripts/extensions/stable-diffusion/style.css +++ b/public/scripts/extensions/stable-diffusion/style.css @@ -1,4 +1,4 @@ -.sd_settings label { +.sd_settings label:not(.checkbox_label) { display: block; } @@ -16,7 +16,6 @@ display: flex; align-items: center; justify-content: center; - } #sd_gen:hover { diff --git a/public/scripts/extensions/tts/elevenlabs.js b/public/scripts/extensions/tts/elevenlabs.js index 0429f5ea8..65c3cf302 100644 --- a/public/scripts/extensions/tts/elevenlabs.js +++ b/public/scripts/extensions/tts/elevenlabs.js @@ -7,6 +7,7 @@ class ElevenLabsTtsProvider { settings voices = [] + separator = ' ... ... ... ' get settings() { return this.settings diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index cca47baf4..31c23ce8b 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -1,4 +1,4 @@ -import { callPopup, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js' +import { callPopup, cancelTtsPlay, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js' import { extension_settings, getContext } from '../../extensions.js' import { getStringHash } from '../../utils.js' import { ElevenLabsTtsProvider } from './elevenlabs.js' @@ -24,9 +24,47 @@ let ttsProviders = { let ttsProvider let ttsProviderName +async function onNarrateOneMessage() { + cancelTtsPlay(); + const context = getContext(); + const id = $(this).closest('.mes').attr('mesid'); + const message = context.chat[id]; + + if (!message) { + return; + } + + currentTtsJob = null; + audioElement.pause(); + audioElement.currentTime = 0; + ttsJobQueue.splice(0, ttsJobQueue.length); + audioJobQueue.splice(0, audioJobQueue.length); + ttsJobQueue.push(message); + moduleWorker(); +} + +let isWorkerBusy = false; + +async function moduleWorkerWrapper() { + // Don't touch me I'm busy... + if (isWorkerBusy) { + return; + } + + // I'm free. Let's update! + try { + isWorkerBusy = true; + await moduleWorker(); + } + finally { + isWorkerBusy = false; + } +} + async function moduleWorker() { // Primarily determinign when to add new chat to the TTS queue const enabled = $('#tts_enabled').is(':checked') + $('body').toggleClass('tts', enabled); if (!enabled) { return } @@ -163,7 +201,7 @@ function onAudioControlClicked() { function addAudioControl() { $('#send_but_sheld').prepend('
') - $('#tts_media_control').on('click', onAudioControlClicked) + $('#tts_media_control').attr('title', 'TTS play/pause').on('click', onAudioControlClicked) audioControl = document.getElementById('tts_media_control') updateUiAudioPlayState() } @@ -247,7 +285,8 @@ async function processTtsQueue() { const special_quotes = /[“”]/g; // Extend this regex to include other special quotes text = text.replace(special_quotes, '"'); const matches = text.match(/".*?"/g); // Matches text inside double quotes, non-greedily - text = matches ? matches.join(' ... ... ... ') : text; + const partJoiner = (ttsProvider?.separator || ' ... '); + text = matches ? matches.join(partJoiner) : text; } console.log(`TTS: ${text}`) const char = currentTtsJob.name @@ -295,6 +334,7 @@ function loadSettings() { ) $('#tts_narrate_dialogues').prop('checked', extension_settings.tts.narrate_dialogues_only) $('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only) + $('body').toggleClass('tts', extension_settings.tts.enabled); } const defaultSettings = { @@ -506,10 +546,11 @@ $(document).ready(function () { $('#tts_provider').append($("