mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2024-12-17 03:39:06 +01:00
8932 lines
325 KiB
JavaScript
8932 lines
325 KiB
JavaScript
import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods, } from "./scripts/RossAscends-mods.js";
|
||
import { userStatsHandler, statMesProcess } from './scripts/stats.js';
|
||
import {
|
||
generateKoboldWithStreaming,
|
||
kai_settings,
|
||
loadKoboldSettings,
|
||
formatKoboldUrl,
|
||
getKoboldGenerationData,
|
||
kai_flags,
|
||
setKoboldFlags,
|
||
} from "./scripts/kai-settings.js";
|
||
|
||
import {
|
||
textgenerationwebui_settings,
|
||
loadTextGenSettings,
|
||
generateTextGenWithStreaming,
|
||
getTextGenGenerationData,
|
||
formatTextGenURL,
|
||
} from "./scripts/textgen-settings.js";
|
||
|
||
import {
|
||
world_info,
|
||
getWorldInfoPrompt,
|
||
getWorldInfoSettings,
|
||
setWorldInfoSettings,
|
||
world_names,
|
||
importEmbeddedWorldInfo,
|
||
checkEmbeddedWorld,
|
||
setWorldInfoButtonClass,
|
||
importWorldInfo,
|
||
} from "./scripts/world-info.js";
|
||
|
||
import {
|
||
groups,
|
||
selected_group,
|
||
saveGroupChat,
|
||
getGroups,
|
||
generateGroupWrapper,
|
||
deleteGroup,
|
||
is_group_generating,
|
||
resetSelectedGroup,
|
||
select_group_chats,
|
||
regenerateGroup,
|
||
group_generation_id,
|
||
getGroupChat,
|
||
renameGroupMember,
|
||
createNewGroupChat,
|
||
getGroupPastChats,
|
||
getGroupAvatar,
|
||
openGroupChat,
|
||
editGroup,
|
||
deleteGroupChat,
|
||
renameGroupChat,
|
||
importGroupChat,
|
||
getGroupBlock,
|
||
getGroupChatNames,
|
||
} from "./scripts/group-chats.js";
|
||
|
||
import {
|
||
collapseNewlines,
|
||
loadPowerUserSettings,
|
||
playMessageSound,
|
||
fixMarkdown,
|
||
power_user,
|
||
persona_description_positions,
|
||
loadMovingUIState,
|
||
getCustomStoppingStrings,
|
||
MAX_CONTEXT_DEFAULT,
|
||
renderStoryString,
|
||
sortEntitiesList,
|
||
registerDebugFunction,
|
||
ui_mode,
|
||
switchSimpleMode,
|
||
} from "./scripts/power-user.js";
|
||
|
||
import {
|
||
setOpenAIMessageExamples,
|
||
setOpenAIMessages,
|
||
setupChatCompletionPromptManager,
|
||
prepareOpenAIMessages,
|
||
sendOpenAIRequest,
|
||
loadOpenAISettings,
|
||
setOpenAIOnlineStatus,
|
||
generateOpenAIPromptCache,
|
||
oai_settings,
|
||
is_get_status_openai,
|
||
openai_messages_count,
|
||
chat_completion_sources,
|
||
getChatCompletionModel,
|
||
} from "./scripts/openai.js";
|
||
|
||
import {
|
||
generateNovelWithStreaming,
|
||
getNovelGenerationData,
|
||
getKayraMaxContextTokens,
|
||
getNovelTier,
|
||
loadNovelPreset,
|
||
loadNovelSettings,
|
||
nai_settings,
|
||
adjustNovelInstructionPrompt,
|
||
loadNovelSubscriptionData,
|
||
} from "./scripts/nai-settings.js";
|
||
|
||
import {
|
||
createNewBookmark,
|
||
showBookmarksButtons,
|
||
createBranch,
|
||
} from "./scripts/bookmarks.js";
|
||
|
||
import {
|
||
horde_settings,
|
||
loadHordeSettings,
|
||
generateHorde,
|
||
checkHordeStatus,
|
||
getHordeModels,
|
||
adjustHordeGenerationParams,
|
||
MIN_AMOUNT_GEN,
|
||
} from "./scripts/horde.js";
|
||
|
||
import {
|
||
debounce,
|
||
delay,
|
||
restoreCaretPosition,
|
||
saveCaretPosition,
|
||
trimToEndSentence,
|
||
countOccurrences,
|
||
isOdd,
|
||
sortMoments,
|
||
timestampToMoment,
|
||
download,
|
||
isDataURL,
|
||
getCharaFilename,
|
||
isDigitsOnly,
|
||
PAGINATION_TEMPLATE,
|
||
waitUntilCondition,
|
||
escapeRegex,
|
||
resetScrollHeight,
|
||
onlyUnique,
|
||
} from "./scripts/utils.js";
|
||
|
||
import { extension_settings, getContext, loadExtensionSettings, processExtensionHelpers, registerExtensionHelper, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js";
|
||
import { COMMENT_NAME_DEFAULT, executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "./scripts/slash-commands.js";
|
||
import {
|
||
tag_map,
|
||
tags,
|
||
loadTagsSettings,
|
||
printTagFilters,
|
||
getTagsList,
|
||
appendTagToList,
|
||
createTagMapFromList,
|
||
renameTagKey,
|
||
importTags,
|
||
tag_filter_types,
|
||
} from "./scripts/tags.js";
|
||
import {
|
||
SECRET_KEYS,
|
||
readSecretState,
|
||
secret_state,
|
||
writeSecret
|
||
} from "./scripts/secrets.js";
|
||
import { EventEmitter } from './lib/eventemitter.js';
|
||
import { markdownExclusionExt } from "./scripts/showdown-exclusion.js";
|
||
import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from "./scripts/authors-note.js";
|
||
import { getDeviceInfo } from "./scripts/RossAscends-mods.js";
|
||
import { registerPromptManagerMigration } from "./scripts/PromptManager.js";
|
||
import { getRegexedString, regex_placement } from "./scripts/extensions/regex/engine.js";
|
||
import { FILTER_TYPES, FilterHelper } from "./scripts/filters.js";
|
||
import { getCfgPrompt, getGuidanceScale } from "./scripts/extensions/cfg/util.js";
|
||
import {
|
||
force_output_sequence,
|
||
formatInstructModeChat,
|
||
formatInstructModePrompt,
|
||
formatInstructModeExamples,
|
||
getInstructStoppingSequences,
|
||
autoSelectInstructPreset,
|
||
formatInstructModeSystemPrompt,
|
||
} from "./scripts/instruct-mode.js";
|
||
import { applyLocale } from "./scripts/i18n.js";
|
||
import { getTokenCount, getTokenizerModel, saveTokenCache } from "./scripts/tokenizers.js";
|
||
import { initPersonas, selectCurrentPersona, setPersonaDescription } from "./scripts/personas.js";
|
||
import { loadMancerModels } from "./scripts/mancer-settings.js";
|
||
|
||
//exporting functions and vars for mods
|
||
export {
|
||
Generate,
|
||
getSettings,
|
||
saveSettings,
|
||
saveSettingsDebounced,
|
||
printMessages,
|
||
clearChat,
|
||
getChat,
|
||
getCharacters,
|
||
callPopup,
|
||
substituteParams,
|
||
sendSystemMessage,
|
||
addOneMessage,
|
||
deleteLastMessage,
|
||
resetChatState,
|
||
select_rm_info,
|
||
setCharacterId,
|
||
setCharacterName,
|
||
replaceCurrentChat,
|
||
setOnlineStatus,
|
||
checkOnlineStatus,
|
||
setEditedMessageId,
|
||
setSendButtonState,
|
||
selectRightMenuWithAnimation,
|
||
openCharacterChat,
|
||
saveChat,
|
||
messageFormatting,
|
||
getExtensionPrompt,
|
||
showSwipeButtons,
|
||
hideSwipeButtons,
|
||
changeMainAPI,
|
||
setGenerationProgress,
|
||
updateChatMetadata,
|
||
scrollChatToBottom,
|
||
isStreamingEnabled,
|
||
getThumbnailUrl,
|
||
getStoppingStrings,
|
||
getStatus,
|
||
reloadMarkdownProcessor,
|
||
getCurrentChatId,
|
||
chat,
|
||
this_chid,
|
||
selected_button,
|
||
menu_type,
|
||
settings,
|
||
characters,
|
||
online_status,
|
||
main_api,
|
||
api_server,
|
||
system_messages,
|
||
nai_settings,
|
||
token,
|
||
name1,
|
||
name2,
|
||
is_send_press,
|
||
api_server_textgenerationwebui,
|
||
max_context,
|
||
chat_metadata,
|
||
streamingProcessor,
|
||
default_avatar,
|
||
system_message_types,
|
||
talkativeness_default,
|
||
default_ch_mes,
|
||
extension_prompt_types,
|
||
mesForShowdownParse,
|
||
printCharacters,
|
||
}
|
||
|
||
// Allow target="_blank" in links
|
||
DOMPurify.addHook('afterSanitizeAttributes', function (node) {
|
||
if ('target' in node) {
|
||
node.setAttribute('target', '_blank');
|
||
node.setAttribute('rel', 'noopener');
|
||
}
|
||
});
|
||
|
||
// API OBJECT FOR EXTERNAL WIRING
|
||
window["SillyTavern"] = {};
|
||
|
||
// Event source init
|
||
export const event_types = {
|
||
EXTRAS_CONNECTED: 'extras_connected',
|
||
MESSAGE_SWIPED: 'message_swiped',
|
||
MESSAGE_SENT: 'message_sent',
|
||
MESSAGE_RECEIVED: 'message_received',
|
||
MESSAGE_EDITED: 'message_edited',
|
||
MESSAGE_DELETED: 'message_deleted',
|
||
IMPERSONATE_READY: 'impersonate_ready',
|
||
CHAT_CHANGED: 'chat_id_changed',
|
||
GENERATION_STOPPED: 'generation_stopped',
|
||
EXTENSIONS_FIRST_LOAD: 'extensions_first_load',
|
||
SETTINGS_LOADED: 'settings_loaded',
|
||
SETTINGS_UPDATED: 'settings_updated',
|
||
GROUP_UPDATED: 'group_updated',
|
||
MOVABLE_PANELS_RESET: 'movable_panels_reset',
|
||
SETTINGS_LOADED_BEFORE: 'settings_loaded_before',
|
||
SETTINGS_LOADED_AFTER: 'settings_loaded_after',
|
||
CHATCOMPLETION_SOURCE_CHANGED: 'chatcompletion_source_changed',
|
||
CHATCOMPLETION_MODEL_CHANGED: 'chatcompletion_model_changed',
|
||
OAI_BEFORE_CHATCOMPLETION: 'oai_before_chatcompletion',
|
||
OAI_PRESET_CHANGED_BEFORE: 'oai_preset_changed_before',
|
||
OAI_PRESET_CHANGED_AFTER: 'oai_preset_changed_after',
|
||
WORLDINFO_SETTINGS_UPDATED: 'worldinfo_settings_updated',
|
||
CHARACTER_EDITED: 'character_edited',
|
||
USER_MESSAGE_RENDERED: 'user_message_rendered',
|
||
CHARACTER_MESSAGE_RENDERED: 'character_message_rendered',
|
||
FORCE_SET_BACKGROUND: 'force_set_background',
|
||
CHAT_DELETED: 'chat_deleted',
|
||
GROUP_CHAT_DELETED: 'group_chat_deleted',
|
||
}
|
||
|
||
export const eventSource = new EventEmitter();
|
||
|
||
// Check for override warnings every 5 seconds...
|
||
setInterval(displayOverrideWarnings, 5000);
|
||
// ...or when the chat changes
|
||
eventSource.on(event_types.SETTINGS_LOADED, () => { settingsReady = true; });
|
||
eventSource.on(event_types.CHAT_CHANGED, displayOverrideWarnings);
|
||
eventSource.on(event_types.MESSAGE_RECEIVED, processExtensionHelpers);
|
||
eventSource.on(event_types.MESSAGE_SENT, processExtensionHelpers);
|
||
|
||
hljs.addPlugin({ "before:highlightElement": ({ el }) => { el.textContent = el.innerText } });
|
||
|
||
// Markdown converter
|
||
let mesForShowdownParse; //intended to be used as a context to compare showdown strings against
|
||
let converter;
|
||
reloadMarkdownProcessor();
|
||
|
||
// array for prompt token calculations
|
||
console.debug('initializing Prompt Itemization Array on Startup');
|
||
let itemizedPrompts = [];
|
||
|
||
/* let bg_menu_toggle = false; */
|
||
export const systemUserName = "SillyTavern System";
|
||
let default_user_name = "User";
|
||
let name1 = default_user_name;
|
||
let name2 = "SillyTavern System";
|
||
let chat = [];
|
||
let safetychat = [
|
||
{
|
||
name: systemUserName,
|
||
is_user: false,
|
||
create_date: 0,
|
||
mes: "You deleted a character/chat and arrived back here for safety reasons! Pick another character!",
|
||
},
|
||
];
|
||
let chatSaveTimeout;
|
||
let importFlashTimeout;
|
||
export let isChatSaving = false;
|
||
let chat_create_date = 0;
|
||
let firstRun = false;
|
||
let settingsReady = false;
|
||
|
||
const default_ch_mes = "Hello";
|
||
let count_view_mes = 0;
|
||
let generatedPromptCache = "";
|
||
let generation_started = new Date();
|
||
let characters = [];
|
||
let this_chid;
|
||
let saveCharactersPage = 0;
|
||
let backgrounds = [];
|
||
const default_avatar = "img/ai4.png";
|
||
export const system_avatar = "img/five.png";
|
||
export const comment_avatar = "img/quill.png";
|
||
export let CLIENT_VERSION = 'SillyTavern:UNKNOWN:Cohee#1207'; // For Horde header
|
||
let optionsPopper = Popper.createPopper(document.getElementById('options_button'), document.getElementById('options'), {
|
||
placement: 'top-start'
|
||
});
|
||
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;
|
||
let scrollLock = false;
|
||
|
||
const durationSaveEdit = 1000;
|
||
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
|
||
export const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit);
|
||
|
||
const system_message_types = {
|
||
HELP: "help",
|
||
WELCOME: "welcome",
|
||
GROUP: "group",
|
||
EMPTY: "empty",
|
||
GENERIC: "generic",
|
||
BOOKMARK_CREATED: "bookmark_created",
|
||
BOOKMARK_BACK: "bookmark_back",
|
||
NARRATOR: "narrator",
|
||
COMMENT: "comment",
|
||
SLASH_COMMANDS: "slash_commands",
|
||
FORMATTING: "formatting",
|
||
HOTKEYS: "hotkeys",
|
||
MACROS: "macros",
|
||
};
|
||
|
||
const extension_prompt_types = {
|
||
IN_PROMPT: 0,
|
||
IN_CHAT: 1,
|
||
BEFORE_PROMPT: 2
|
||
};
|
||
|
||
export const MAX_INJECTION_DEPTH = 1000;
|
||
|
||
let system_messages = {};
|
||
|
||
function getSystemMessages() {
|
||
system_messages = {
|
||
help: {
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
mes: renderTemplate("help"),
|
||
},
|
||
slash_commands: {
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
mes: '',
|
||
},
|
||
hotkeys: {
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
mes: renderTemplate("hotkeys"),
|
||
},
|
||
formatting: {
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
mes: renderTemplate("formatting"),
|
||
},
|
||
macros: {
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
mes: renderTemplate("macros"),
|
||
},
|
||
welcome:
|
||
{
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
mes: renderTemplate("welcome"),
|
||
},
|
||
group: {
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
is_group: true,
|
||
mes: "Group chat created. Say 'Hi' to lovely people!",
|
||
},
|
||
empty: {
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
mes: "No one hears you. <b>Hint:</b> add more members to the group!",
|
||
},
|
||
generic: {
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
mes: "Generic system message. User `text` parameter to override the contents",
|
||
},
|
||
bookmark_created: {
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
mes: `Bookmark created! Click here to open the bookmark chat: <a class="bookmark_link" file_name="{0}" href="javascript:void(null);">{1}</a>`,
|
||
},
|
||
bookmark_back: {
|
||
name: systemUserName,
|
||
force_avatar: system_avatar,
|
||
is_user: false,
|
||
is_system: true,
|
||
mes: `Click here to return to the previous chat: <a class="bookmark_link" file_name="{0}" href="javascript:void(null);">Return</a>`,
|
||
},
|
||
};
|
||
}
|
||
|
||
// Register configuration migrations
|
||
registerPromptManagerMigration();
|
||
|
||
$(document).ajaxError(function myErrorHandler(_, xhr) {
|
||
if (xhr.status == 403) {
|
||
toastr.warning(
|
||
"doubleCsrf errors in console are NORMAL in this case. If you want to run ST in multiple tabs, start the server with --disableCsrf option.",
|
||
"Looks like you've opened SillyTavern in another browser tab",
|
||
{ timeOut: 0, extendedTimeOut: 0, preventDuplicates: true },
|
||
);
|
||
}
|
||
});
|
||
|
||
function getUrlSync(url, cache = true) {
|
||
return $.ajax({
|
||
type: "GET",
|
||
url: url,
|
||
cache: cache,
|
||
async: false
|
||
}).responseText;
|
||
}
|
||
|
||
const templateCache = {};
|
||
|
||
export function renderTemplate(templateId, templateData = {}, sanitize = true, localize = true, fullPath = false) {
|
||
try {
|
||
const pathToTemplate = fullPath ? templateId : `/scripts/templates/${templateId}.html`;
|
||
const templateContent = (pathToTemplate in templateCache) ? templateCache[pathToTemplate] : getUrlSync(pathToTemplate);
|
||
templateCache[pathToTemplate] = templateContent;
|
||
const template = Handlebars.compile(templateContent);
|
||
let result = template(templateData);
|
||
|
||
if (sanitize) {
|
||
result = DOMPurify.sanitize(result);
|
||
}
|
||
|
||
if (localize) {
|
||
result = applyLocale(result);
|
||
}
|
||
|
||
return result;
|
||
} catch (err) {
|
||
console.error("Error rendering template", templateId, templateData, err);
|
||
toastr.error("Check the DevTools console for more information.", "Error rendering template");
|
||
}
|
||
}
|
||
|
||
async function getClientVersion() {
|
||
try {
|
||
const response = await fetch('/version');
|
||
const data = await response.json();
|
||
CLIENT_VERSION = data.agent;
|
||
let displayVersion = `SillyTavern ${data.pkgVersion}`;
|
||
|
||
if (data.gitRevision && data.gitBranch) {
|
||
displayVersion += ` '${data.gitBranch}' (${data.gitRevision})`;
|
||
}
|
||
|
||
$('#version_display').text(displayVersion);
|
||
$('#version_display_welcome').text(displayVersion);
|
||
} catch (err) {
|
||
console.error("Couldn't get client version", err);
|
||
}
|
||
}
|
||
|
||
function reloadMarkdownProcessor(render_formulas = false) {
|
||
if (render_formulas) {
|
||
converter = new showdown.Converter({
|
||
emoji: true,
|
||
underline: true,
|
||
tables: true,
|
||
parseImgDimensions: true,
|
||
extensions: [
|
||
showdownKatex(
|
||
{
|
||
delimiters: [
|
||
{ left: '$$', right: '$$', display: true, asciimath: false },
|
||
{ left: '$', right: '$', display: false, asciimath: true },
|
||
]
|
||
}
|
||
)],
|
||
});
|
||
}
|
||
else {
|
||
converter = new showdown.Converter({
|
||
emoji: true,
|
||
literalMidWordUnderscores: true,
|
||
parseImgDimensions: true,
|
||
tables: true,
|
||
});
|
||
}
|
||
|
||
// Inject the dinkus extension after creating the converter
|
||
// Maybe move this into power_user init?
|
||
setTimeout(() => {
|
||
if (power_user) {
|
||
converter.addExtension(markdownExclusionExt(), 'exclusion');
|
||
}
|
||
}, 1)
|
||
|
||
return converter;
|
||
}
|
||
|
||
function getCurrentChatId() {
|
||
if (selected_group) {
|
||
return groups.find(x => x.id == selected_group)?.chat_id;
|
||
}
|
||
else if (this_chid) {
|
||
return characters[this_chid].chat;
|
||
}
|
||
}
|
||
|
||
const talkativeness_default = 0.5;
|
||
|
||
var is_advanced_char_open = false;
|
||
|
||
var menu_type = ""; //what is selected in the menu
|
||
var selected_button = ""; //which button pressed
|
||
//create pole save
|
||
let create_save = {
|
||
name: "",
|
||
description: "",
|
||
creator_notes: "",
|
||
post_history_instructions: "",
|
||
character_version: "",
|
||
system_prompt: "",
|
||
tags: "",
|
||
creator: "",
|
||
personality: "",
|
||
first_message: "",
|
||
avatar: "",
|
||
scenario: "",
|
||
mes_example: "",
|
||
world: "",
|
||
talkativeness: talkativeness_default,
|
||
alternate_greetings: []
|
||
};
|
||
|
||
//animation right menu
|
||
let animation_duration = 125;
|
||
let animation_easing = "ease-in-out";
|
||
let popup_type = "";
|
||
let bg_file_for_del = "";
|
||
let chat_file_for_del = "";
|
||
let online_status = "no_connection";
|
||
|
||
let api_server = "";
|
||
let api_server_textgenerationwebui = "";
|
||
//var interval_timer = setInterval(getStatus, 2000);
|
||
//let interval_timer_novel = setInterval(getStatusNovel, 90000);
|
||
let is_get_status = false;
|
||
let is_get_status_novel = false;
|
||
let is_api_button_press = false;
|
||
let is_api_button_press_novel = false;
|
||
let api_use_mancer_webui = false;
|
||
|
||
let is_send_press = false; //Send generation
|
||
|
||
let this_del_mes = 0;
|
||
|
||
//message editing and chat scroll position persistence
|
||
var this_edit_mes_text = "";
|
||
var this_edit_mes_chname = "";
|
||
var this_edit_mes_id;
|
||
var scroll_holder = 0;
|
||
var is_use_scroll_holder = false;
|
||
|
||
//settings
|
||
var settings;
|
||
export let koboldai_settings;
|
||
export let koboldai_setting_names;
|
||
var preset_settings = "gui";
|
||
export let user_avatar = "you.png";
|
||
export var amount_gen = 80; //default max length of AI generated responses
|
||
var max_context = 2048;
|
||
|
||
var message_already_generated = "";
|
||
|
||
var swipes = true;
|
||
let extension_prompts = {};
|
||
|
||
var main_api;// = "kobold";
|
||
//novel settings
|
||
export let novelai_settings;
|
||
export let novelai_setting_names;
|
||
let abortController;
|
||
|
||
//css
|
||
var css_mes_bg = $('<div class="mes"></div>').css("background");
|
||
var css_send_form_display = $("<div id=send_form></div>").css("display");
|
||
let generate_loop_counter = 0;
|
||
const MAX_GENERATION_LOOPS = 5;
|
||
|
||
var kobold_horde_model = "";
|
||
|
||
let token;
|
||
|
||
var PromptArrayItemForRawPromptDisplay;
|
||
|
||
export let active_character = "";
|
||
export let active_group = "";
|
||
export const entitiesFilter = new FilterHelper(debounce(printCharacters, 100));
|
||
|
||
export function getRequestHeaders() {
|
||
return {
|
||
"Content-Type": "application/json",
|
||
"X-CSRF-Token": token,
|
||
};
|
||
}
|
||
|
||
$.ajaxPrefilter((options, originalOptions, xhr) => {
|
||
xhr.setRequestHeader("X-CSRF-Token", token);
|
||
});
|
||
|
||
async function firstLoadInit() {
|
||
try {
|
||
const tokenResponse = await fetch('/csrf-token');
|
||
const tokenData = await tokenResponse.json();
|
||
token = tokenData.token;
|
||
} catch {
|
||
toastr.error("Couldn't get CSRF token. Please refresh the page.", "Error", { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true });
|
||
throw new Error("Initialization failed");
|
||
}
|
||
|
||
getSystemMessages();
|
||
sendSystemMessage(system_message_types.WELCOME);
|
||
await readSecretState();
|
||
await getClientVersion();
|
||
await getSettings("def");
|
||
await getUserAvatars();
|
||
await getCharacters();
|
||
await getBackgrounds();
|
||
initAuthorsNote();
|
||
initPersonas();
|
||
initRossMods();
|
||
}
|
||
|
||
function checkOnlineStatus() {
|
||
///////// REMOVED LINES THAT DUPLICATE RA_CHeckOnlineStatus FEATURES
|
||
if (online_status == "no_connection") {
|
||
$("#online_status_indicator2").css("background-color", "red"); //Kobold
|
||
$("#online_status_text2").html("No connection...");
|
||
$("#online_status_indicator_horde").css("background-color", "red"); //Kobold Horde
|
||
$("#online_status_text_horde").html("No connection...");
|
||
$("#online_status_indicator3").css("background-color", "red"); //Novel
|
||
$("#online_status_text3").html("No connection...");
|
||
$(".online_status_indicator4").css("background-color", "red"); //OAI / ooba
|
||
$(".online_status_text4").html("No connection...");
|
||
is_get_status = false;
|
||
is_get_status_novel = false;
|
||
setOpenAIOnlineStatus(false);
|
||
} else {
|
||
$("#online_status_indicator2").css("background-color", "green"); //kobold
|
||
$("#online_status_text2").html(online_status);
|
||
$("#online_status_indicator_horde").css("background-color", "green"); //Kobold Horde
|
||
$("#online_status_text_horde").html(online_status);
|
||
$("#online_status_indicator3").css("background-color", "green"); //novel
|
||
$("#online_status_text3").html(online_status);
|
||
$(".online_status_indicator4").css("background-color", "green"); //OAI / ooba
|
||
$(".online_status_text4").html(online_status);
|
||
}
|
||
}
|
||
|
||
export function setActiveCharacter(character) {
|
||
active_character = character;
|
||
}
|
||
|
||
export function setActiveGroup(group) {
|
||
active_group = group;
|
||
}
|
||
|
||
async function getStatus() {
|
||
if (is_get_status) {
|
||
if (main_api == "koboldhorde") {
|
||
try {
|
||
const hordeStatus = await checkHordeStatus();
|
||
online_status = hordeStatus ? 'Connected' : 'no_connection';
|
||
resultCheckStatus();
|
||
}
|
||
catch {
|
||
online_status = "no_connection";
|
||
resultCheckStatus();
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
jQuery.ajax({
|
||
type: "POST", //
|
||
url: "/getstatus", //
|
||
data: JSON.stringify({
|
||
api_server: main_api == "kobold" ? api_server : api_server_textgenerationwebui,
|
||
main_api: main_api,
|
||
use_mancer: main_api == "textgenerationwebui" ? api_use_mancer_webui : false,
|
||
}),
|
||
beforeSend: function () { },
|
||
cache: false,
|
||
dataType: "json",
|
||
crossDomain: true,
|
||
contentType: "application/json",
|
||
//processData: false,
|
||
success: function (data) {
|
||
online_status = data.result;
|
||
if (online_status == undefined) {
|
||
online_status = "no_connection";
|
||
}
|
||
|
||
// Determine instruct mode preset
|
||
autoSelectInstructPreset(online_status);
|
||
|
||
// determine if we can use stop sequence and streaming
|
||
if (main_api === "kobold" || main_api === "koboldhorde") {
|
||
setKoboldFlags(data.version, data.koboldVersion);
|
||
}
|
||
|
||
// We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
|
||
if (online_status == "no_connection" && data.response) {
|
||
toastr.error(data.response, "API Error", { timeOut: 5000, preventDuplicates: true })
|
||
}
|
||
|
||
//console.log(online_status);
|
||
resultCheckStatus();
|
||
},
|
||
error: function (jqXHR, exception) {
|
||
console.log(exception);
|
||
console.log(jqXHR);
|
||
online_status = "no_connection";
|
||
|
||
resultCheckStatus();
|
||
},
|
||
});
|
||
} else {
|
||
if (is_get_status_novel != true && is_get_status_openai != true) {
|
||
online_status = "no_connection";
|
||
}
|
||
}
|
||
}
|
||
|
||
function resultCheckStatus() {
|
||
is_api_button_press = false;
|
||
checkOnlineStatus();
|
||
$("#api_loading").css("display", "none");
|
||
$("#api_button").css("display", "inline-block");
|
||
$("#api_loading_textgenerationwebui").css("display", "none");
|
||
$("#api_button_textgenerationwebui").css("display", "inline-block");
|
||
}
|
||
|
||
export function selectCharacterById(id) {
|
||
if (characters[id] == undefined) {
|
||
return;
|
||
}
|
||
|
||
if (isChatSaving) {
|
||
toastr.info("Please wait until the chat is saved before switching characters.", "Your chat is still saving...");
|
||
return;
|
||
}
|
||
|
||
if (selected_group && is_group_generating) {
|
||
return;
|
||
}
|
||
|
||
if (selected_group || this_chid !== id) {
|
||
//if clicked on a different character from what was currently selected
|
||
if (!is_send_press) {
|
||
cancelTtsPlay();
|
||
resetSelectedGroup();
|
||
this_edit_mes_id = undefined;
|
||
selected_button = "character_edit";
|
||
this_chid = id;
|
||
clearChat();
|
||
chat.length = 0;
|
||
chat_metadata = {};
|
||
getChat();
|
||
}
|
||
} else {
|
||
//if clicked on character that was already selected
|
||
selected_button = "character_edit";
|
||
select_selected_character(this_chid);
|
||
}
|
||
}
|
||
|
||
function getCharacterBlock(item, id) {
|
||
let this_avatar = default_avatar;
|
||
if (item.avatar != "none") {
|
||
this_avatar = getThumbnailUrl('avatar', item.avatar);
|
||
}
|
||
// Populate the template
|
||
const template = $('#character_template .character_select').clone();
|
||
template.attr({ 'chid': id, 'id': `CharID${id}` });
|
||
template.find('img').attr('src', this_avatar);
|
||
template.find('.avatar').attr('title', item.avatar);
|
||
template.find('.ch_name').text(item.name);
|
||
if (power_user.show_card_avatar_urls) {
|
||
template.find('.ch_avatar_url').text(item.avatar);
|
||
}
|
||
template.find('.ch_fav_icon').css("display", 'none');
|
||
template.toggleClass('is_fav', item.fav || item.fav == 'true');
|
||
template.find('.ch_fav').val(item.fav);
|
||
|
||
const description = item.data?.creator_notes?.split('\n', 1)[0] || '';
|
||
if (description) {
|
||
template.find('.ch_description').text(description);
|
||
}
|
||
else {
|
||
template.find('.ch_description').hide();
|
||
}
|
||
|
||
const version = item.data?.character_version || '';
|
||
if (version) {
|
||
template.find('.character_version').text(version);
|
||
}
|
||
else {
|
||
template.find('.character_version').hide();
|
||
}
|
||
|
||
// Display inline tags
|
||
const tags = getTagsList(item.avatar);
|
||
const tagsElement = template.find('.tags');
|
||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||
|
||
// Add to the list
|
||
return template;
|
||
}
|
||
|
||
async function printCharacters(fullRefresh = false) {
|
||
if (fullRefresh) {
|
||
saveCharactersPage = 0;
|
||
printTagFilters(tag_filter_types.character);
|
||
printTagFilters(tag_filter_types.group_member);
|
||
|
||
await delay(1);
|
||
displayOverrideWarnings();
|
||
}
|
||
|
||
const storageKey = 'Characters_PerPage';
|
||
$("#rm_print_characters_pagination").pagination({
|
||
dataSource: getEntitiesList({ doFilter: true }),
|
||
pageSize: Number(localStorage.getItem(storageKey)) || 50,
|
||
sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000],
|
||
pageRange: 1,
|
||
pageNumber: saveCharactersPage || 1,
|
||
position: 'top',
|
||
showPageNumbers: false,
|
||
showSizeChanger: true,
|
||
prevText: '<',
|
||
nextText: '>',
|
||
formatNavigator: PAGINATION_TEMPLATE,
|
||
showNavigator: true,
|
||
callback: function (data) {
|
||
$("#rm_print_characters_block").empty();
|
||
for (const i of data) {
|
||
if (i.type === 'character') {
|
||
$("#rm_print_characters_block").append(getCharacterBlock(i.item, i.id));
|
||
}
|
||
if (i.type === 'group') {
|
||
$("#rm_print_characters_block").append(getGroupBlock(i.item));
|
||
}
|
||
}
|
||
},
|
||
afterSizeSelectorChange: function (e) {
|
||
localStorage.setItem(storageKey, e.target.value);
|
||
},
|
||
afterPaging: function (e) {
|
||
saveCharactersPage = e;
|
||
},
|
||
afterRender: function () {
|
||
$('#rm_print_characters_block').scrollTop(0);
|
||
},
|
||
});
|
||
|
||
favsToHotswap();
|
||
}
|
||
|
||
export function getEntitiesList({ doFilter } = {}) {
|
||
let entities = [];
|
||
entities.push(...characters.map((item, index) => ({ item, id: index, type: 'character' })));
|
||
entities.push(...groups.map((item) => ({ item, id: item.id, type: 'group' })));
|
||
|
||
if (doFilter) {
|
||
entities = entitiesFilter.applyFilters(entities);
|
||
}
|
||
|
||
sortEntitiesList(entities);
|
||
return entities;
|
||
}
|
||
|
||
async function getOneCharacter(avatarUrl) {
|
||
const response = await fetch("/getonecharacter", {
|
||
method: "POST",
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({
|
||
avatar_url: avatarUrl,
|
||
}),
|
||
});
|
||
|
||
if (response.ok) {
|
||
const getData = await response.json();
|
||
getData['name'] = DOMPurify.sanitize(getData['name']);
|
||
getData['chat'] = String(getData['chat']);
|
||
|
||
const indexOf = characters.findIndex(x => x.avatar === avatarUrl);
|
||
|
||
if (indexOf !== -1) {
|
||
characters[indexOf] = getData;
|
||
} else {
|
||
toastr.error(`Character ${avatarUrl} not found in the list`, "Error", { timeOut: 5000, preventDuplicates: true });
|
||
}
|
||
}
|
||
}
|
||
|
||
async function getCharacters() {
|
||
var response = await fetch("/getcharacters", {
|
||
method: "POST",
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({
|
||
"": "",
|
||
}),
|
||
});
|
||
if (response.ok === true) {
|
||
var getData = ""; //RossAscends: reset to force array to update to account for deleted character.
|
||
getData = await response.json();
|
||
const load_ch_count = Object.getOwnPropertyNames(getData);
|
||
for (var i = 0; i < load_ch_count.length; i++) {
|
||
characters[i] = [];
|
||
characters[i] = getData[i];
|
||
characters[i]['name'] = DOMPurify.sanitize(characters[i]['name']);
|
||
|
||
// For dropped-in cards
|
||
if (!characters[i]['chat']) {
|
||
characters[i]['chat'] = `${characters[i]['name']} - ${humanizedDateTime()}`;
|
||
}
|
||
|
||
characters[i]['chat'] = String(characters[i]['chat']);
|
||
}
|
||
if (this_chid != undefined && this_chid != "invalid-safety-id") {
|
||
$("#avatar_url_pole").val(characters[this_chid].avatar);
|
||
}
|
||
|
||
await getGroups();
|
||
await printCharacters(true);
|
||
}
|
||
}
|
||
|
||
async function getBackgrounds() {
|
||
const response = await fetch("/getbackgrounds", {
|
||
method: "POST",
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({
|
||
"": "",
|
||
}),
|
||
});
|
||
if (response.ok === true) {
|
||
const getData = await response.json();
|
||
//background = getData;
|
||
//console.log(getData.length);
|
||
$("#bg_menu_content").children('div').remove();
|
||
for (const bg of getData) {
|
||
const template = getBackgroundFromTemplate(bg);
|
||
$("#bg_menu_content").append(template);
|
||
}
|
||
}
|
||
}
|
||
|
||
function getBackgroundFromTemplate(bg) {
|
||
const thumbPath = getThumbnailUrl('bg', bg);
|
||
const template = $('#background_template .bg_example').clone();
|
||
template.attr('bgfile', bg);
|
||
template.attr('title', bg);
|
||
template.find('.bg_button').attr('bgfile', bg);
|
||
template.css('background-image', `url('${thumbPath}')`);
|
||
template.find('.BGSampleTitle').text(bg.slice(0, bg.lastIndexOf('.')));
|
||
return template;
|
||
}
|
||
|
||
async function setBackground(bg) {
|
||
|
||
jQuery.ajax({
|
||
type: "POST", //
|
||
url: "/setbackground", //
|
||
data: JSON.stringify({
|
||
bg: bg,
|
||
}),
|
||
beforeSend: function () {
|
||
|
||
},
|
||
cache: false,
|
||
dataType: "json",
|
||
contentType: "application/json",
|
||
//processData: false,
|
||
success: function (html) { },
|
||
error: function (jqXHR, exception) {
|
||
console.log(exception);
|
||
console.log(jqXHR);
|
||
},
|
||
});
|
||
}
|
||
|
||
async function delBackground(bg) {
|
||
const response = await fetch("/delbackground", {
|
||
method: "POST",
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({
|
||
bg: bg,
|
||
}),
|
||
});
|
||
if (response.ok === true) {
|
||
|
||
}
|
||
}
|
||
|
||
async function delChat(chatfile) {
|
||
const response = await fetch("/delchat", {
|
||
method: "POST",
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({
|
||
chatfile: chatfile,
|
||
avatar_url: characters[this_chid].avatar,
|
||
}),
|
||
});
|
||
if (response.ok === true) {
|
||
// choose another chat if current was deleted
|
||
const name = chatfile.replace('.jsonl', '');
|
||
if (name === characters[this_chid].chat) {
|
||
chat_metadata = {};
|
||
await replaceCurrentChat();
|
||
}
|
||
await eventSource.emit(event_types.CHAT_DELETED, name);
|
||
}
|
||
}
|
||
|
||
async function replaceCurrentChat() {
|
||
clearChat();
|
||
chat.length = 0;
|
||
|
||
const chatsResponse = await fetch("/getallchatsofcharacter", {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({ avatar_url: characters[this_chid].avatar })
|
||
});
|
||
|
||
if (chatsResponse.ok) {
|
||
const chats = Object.values(await chatsResponse.json());
|
||
|
||
// pick existing chat
|
||
if (chats.length && typeof chats[0] === 'object') {
|
||
characters[this_chid].chat = chats[0].file_name.replace('.jsonl', '');
|
||
$("#selected_chat_pole").val(characters[this_chid].chat);
|
||
saveCharacterDebounced();
|
||
await getChat();
|
||
}
|
||
|
||
// start new chat
|
||
else {
|
||
characters[this_chid].chat = name2 + " - " + humanizedDateTime();
|
||
$("#selected_chat_pole").val(characters[this_chid].chat);
|
||
saveCharacterDebounced();
|
||
await getChat();
|
||
}
|
||
}
|
||
}
|
||
|
||
const TRUNCATION_THRESHOLD = 100;
|
||
|
||
function showMoreMessages() {
|
||
let messageId = Number($('#chat').children('.mes').first().attr('mesid'));
|
||
let count = TRUNCATION_THRESHOLD;
|
||
|
||
console.debug('Inserting messages before', messageId, 'count', count, 'chat length', chat.length);
|
||
const prevHeight = $('#chat').prop('scrollHeight');
|
||
|
||
while (messageId > 0 && count > 0) {
|
||
count--;
|
||
messageId--;
|
||
addOneMessage(chat[messageId], { insertBefore: messageId + 1, scroll: false, forceId: messageId });
|
||
}
|
||
|
||
if (messageId == 0) {
|
||
$('#show_more_messages').remove();
|
||
}
|
||
|
||
const newHeight = $('#chat').prop('scrollHeight');
|
||
$('#chat').scrollTop(newHeight - prevHeight);
|
||
}
|
||
|
||
async function printMessages() {
|
||
let startIndex = 0;
|
||
|
||
if (chat.length > TRUNCATION_THRESHOLD) {
|
||
count_view_mes = chat.length - TRUNCATION_THRESHOLD;
|
||
startIndex = count_view_mes;
|
||
$('#chat').append('<div id="show_more_messages">Show more messages</div>');
|
||
}
|
||
|
||
for (let i = startIndex; i < chat.length; i++) {
|
||
const item = chat[i];
|
||
addOneMessage(item, { scroll: i === chat.length - 1 });
|
||
}
|
||
}
|
||
|
||
function clearChat() {
|
||
count_view_mes = 0;
|
||
extension_prompts = {};
|
||
$("#chat").children().remove();
|
||
if ($('.zoomed_avatar[forChar]').length) {
|
||
console.debug('saw avatars to remove')
|
||
$('.zoomed_avatar[forChar]').remove();
|
||
} else { console.debug('saw no avatars') }
|
||
itemizedPrompts = [];
|
||
}
|
||
|
||
async function deleteLastMessage() {
|
||
count_view_mes--;
|
||
chat.length = chat.length - 1;
|
||
$('#chat').children('.mes').last().remove();
|
||
await eventSource.emit(event_types.MESSAGE_DELETED, chat.length);
|
||
}
|
||
|
||
export async function reloadCurrentChat() {
|
||
clearChat();
|
||
chat.length = 0;
|
||
|
||
if (selected_group) {
|
||
await getGroupChat(selected_group);
|
||
}
|
||
else if (this_chid) {
|
||
await getChat();
|
||
}
|
||
else {
|
||
resetChatState();
|
||
await printMessages();
|
||
}
|
||
|
||
await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId());
|
||
}
|
||
|
||
function messageFormatting(mes, ch_name, isSystem, isUser) {
|
||
if (mes) {
|
||
mesForShowdownParse = mes;
|
||
}
|
||
|
||
if (!mes) {
|
||
mes = '';
|
||
}
|
||
|
||
// Force isSystem = false on comment messages so they get formatted properly
|
||
if (ch_name === COMMENT_NAME_DEFAULT && isSystem && !isUser) {
|
||
isSystem = false;
|
||
}
|
||
|
||
// Prompt bias replacement should be applied on the raw message
|
||
if (!power_user.show_user_prompt_bias && ch_name && !isUser && !isSystem) {
|
||
mes = mes.replaceAll(substituteParams(power_user.user_prompt_bias), "");
|
||
}
|
||
|
||
if (!isSystem) {
|
||
let regexPlacement;
|
||
if (isUser) {
|
||
regexPlacement = regex_placement.USER_INPUT;
|
||
} else if (ch_name !== name2) {
|
||
regexPlacement = regex_placement.SLASH_COMMAND;
|
||
} else {
|
||
regexPlacement = regex_placement.AI_OUTPUT;
|
||
}
|
||
|
||
// Always override the character name
|
||
mes = getRegexedString(mes, regexPlacement, {
|
||
characterOverride: ch_name,
|
||
isMarkdown: true
|
||
});
|
||
}
|
||
|
||
if (power_user.auto_fix_generated_markdown) {
|
||
mes = fixMarkdown(mes, true);
|
||
}
|
||
|
||
if (!isSystem && power_user.encode_tags) {
|
||
mes = mes.replaceAll("<", "<").replaceAll(">", ">");
|
||
}
|
||
|
||
if ((this_chid === undefined || this_chid === "invalid-safety-id") && !selected_group) {
|
||
mes = mes
|
||
.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>")
|
||
.replace(/\n/g, "<br/>");
|
||
} else if (!isSystem) {
|
||
mes = mes.replace(/```[\s\S]*?```|``[\s\S]*?``|`[\s\S]*?`|(\".+?\")|(\u201C.+?\u201D)/gm, function (match, p1, p2) {
|
||
if (p1) {
|
||
return '<q>"' + p1.replace(/\"/g, "") + '"</q>';
|
||
} else if (p2) {
|
||
return '<q>“' + p2.replace(/\u201C|\u201D/g, "") + '”</q>';
|
||
} else {
|
||
return match;
|
||
}
|
||
});
|
||
|
||
mes = mes.replaceAll('\\begin{align*}', '$$');
|
||
mes = mes.replaceAll('\\end{align*}', '$$');
|
||
mes = converter.makeHtml(mes);
|
||
mes = replaceBiasMarkup(mes);
|
||
|
||
mes = mes.replace(/<code(.*)>[\s\S]*?<\/code>/g, function (match) {
|
||
// Firefox creates extra newlines from <br>s in code blocks, so we replace them before converting newlines to <br>s.
|
||
return match.replace(/\n/gm, '\u0000');
|
||
})
|
||
mes = mes.replace(/\n/g, "<br/>");
|
||
mes = mes.replace(/\u0000/g, "\n"); // Restore converted newlines
|
||
mes = mes.trim();
|
||
|
||
mes = mes.replace(/<code(.*)>[\s\S]*?<\/code>/g, function (match) {
|
||
return match.replace(/&/g, '&');
|
||
});
|
||
}
|
||
|
||
/*
|
||
// Hides bias from empty messages send with slash commands
|
||
if (isSystem) {
|
||
mes = mes.replace(/\{\{[\s\S]*?\}\}/gm, "");
|
||
}
|
||
*/
|
||
|
||
if (!power_user.allow_name2_display && ch_name && !isUser && !isSystem) {
|
||
mes = mes.replace(new RegExp(`(^|\n)${ch_name}:`, 'g'), "$1");
|
||
}
|
||
|
||
mes = DOMPurify.sanitize(mes);
|
||
|
||
return mes;
|
||
}
|
||
|
||
/**
|
||
* Inserts or replaces an SVG icon adjacent to the provided message's timestamp.
|
||
*
|
||
* If the `extra.api` is "openai" and `extra.model` contains the substring "claude",
|
||
* the function fetches the "claude.svg". Otherwise, it fetches the SVG named after
|
||
* the value in `extra.api`.
|
||
*
|
||
* @param {JQuery<HTMLElement>} mes - The message element containing the timestamp where the icon should be inserted or replaced.
|
||
* @param {Object} extra - Contains the API and model details.
|
||
* @param {string} extra.api - The name of the API, used to determine which SVG to fetch.
|
||
* @param {string} extra.model - The model name, used to check for the substring "claude".
|
||
*/
|
||
function insertSVGIcon(mes, extra) {
|
||
// Determine the SVG filename
|
||
let modelName;
|
||
|
||
// Claude on OpenRouter or Anthropic
|
||
if (extra.api === "openai" && extra.model?.toLowerCase().includes("claude")) {
|
||
modelName = "claude";
|
||
}
|
||
// OpenAI on OpenRouter
|
||
else if (extra.api === "openai" && extra.model?.toLowerCase().includes("openai")) {
|
||
modelName = "openai";
|
||
}
|
||
// OpenRouter website model or other models
|
||
else if (extra.api === "openai" && (extra.model === null || extra.model?.toLowerCase().includes("/"))) {
|
||
modelName = "openrouter";
|
||
}
|
||
// Everything else
|
||
else {
|
||
modelName = extra.api;
|
||
}
|
||
|
||
const image = new Image();
|
||
// Add classes for styling and identification
|
||
image.classList.add('icon-svg', 'timestamp-icon');
|
||
image.src = `/img/${modelName}.svg`;
|
||
image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`;
|
||
|
||
image.onload = async function () {
|
||
// Check if an SVG already exists adjacent to the timestamp
|
||
let existingSVG = mes.find('.timestamp').next('.timestamp-icon');
|
||
|
||
if (existingSVG.length) {
|
||
// Replace existing SVG
|
||
existingSVG.replaceWith(image);
|
||
} else {
|
||
// Append the new SVG if none exists
|
||
mes.find('.timestamp').after(image);
|
||
}
|
||
|
||
await SVGInject(this);
|
||
};
|
||
}
|
||
|
||
|
||
function getMessageFromTemplate({
|
||
mesId,
|
||
characterName,
|
||
isUser,
|
||
avatarImg,
|
||
bias,
|
||
isSystem,
|
||
title,
|
||
timerValue,
|
||
timerTitle,
|
||
bookmarkLink,
|
||
forceAvatar,
|
||
timestamp,
|
||
tokenCount,
|
||
extra,
|
||
} = {}) {
|
||
const mes = $('#message_template .mes').clone();
|
||
mes.attr({
|
||
'mesid': mesId,
|
||
'ch_name': characterName,
|
||
'is_user': isUser,
|
||
'is_system': !!isSystem,
|
||
'bookmark_link': bookmarkLink,
|
||
'force_avatar': !!forceAvatar,
|
||
'timestamp': timestamp,
|
||
});
|
||
mes.find('.avatar img').attr('src', avatarImg);
|
||
mes.find('.ch_name .name_text').text(characterName);
|
||
mes.find('.mes_bias').html(bias);
|
||
mes.find('.timestamp').text(timestamp).attr('title', `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`);
|
||
mes.find('.mesIDDisplay').text(`#${mesId}`);
|
||
tokenCount && mes.find('.tokenCounterDisplay').text(`${tokenCount}t`);
|
||
title && mes.attr('title', title);
|
||
timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
|
||
|
||
if (power_user.timestamp_model_icon && extra?.api) {
|
||
insertSVGIcon(mes, extra);
|
||
}
|
||
|
||
return mes;
|
||
}
|
||
|
||
export function updateMessageBlock(messageId, message) {
|
||
const messageElement = $(`#chat [mesid="${messageId}"]`);
|
||
const text = message?.extra?.display_text ?? message.mes;
|
||
messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user));
|
||
addCopyToCodeBlocks(messageElement)
|
||
appendImageToMessage(message, messageElement);
|
||
}
|
||
|
||
export function appendImageToMessage(mes, messageElement) {
|
||
if (mes.extra?.image) {
|
||
const image = messageElement.find('.mes_img');
|
||
const text = messageElement.find('.mes_text');
|
||
const isInline = !!mes.extra?.inline_image;
|
||
image.attr('src', mes.extra?.image);
|
||
image.attr('title', mes.extra?.title || mes.title || '');
|
||
messageElement.find(".mes_img_container").addClass("img_extra");
|
||
image.toggleClass("img_inline", isInline);
|
||
text.toggleClass('displayNone', !isInline);
|
||
}
|
||
}
|
||
|
||
export function addCopyToCodeBlocks(messageElement) {
|
||
const codeBlocks = $(messageElement).find("pre code");
|
||
for (let i = 0; i < codeBlocks.length; i++) {
|
||
hljs.highlightElement(codeBlocks.get(i));
|
||
if (navigator.clipboard !== undefined) {
|
||
const copyButton = document.createElement('i');
|
||
copyButton.classList.add('fa-solid', 'fa-copy', 'code-copy');
|
||
copyButton.title = 'Copy code';
|
||
codeBlocks.get(i).appendChild(copyButton);
|
||
copyButton.addEventListener('pointerup', function (event) {
|
||
navigator.clipboard.writeText(codeBlocks.get(i).innerText);
|
||
toastr.info('Copied!', '', { timeOut: 2000 });
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true, insertBefore = null, forceId = null } = {}) {
|
||
var messageText = mes["mes"];
|
||
const momentDate = timestampToMoment(mes.send_date);
|
||
const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';
|
||
|
||
if (mes?.extra?.display_text) {
|
||
messageText = mes.extra.display_text;
|
||
}
|
||
|
||
// Forbidden black magic
|
||
// This allows to use "continue" on user messages
|
||
if (type === 'swipe' && mes.swipe_id === undefined) {
|
||
mes.swipe_id = 0;
|
||
mes.swipes = [mes.mes];
|
||
}
|
||
|
||
if (mes.name === name1) {
|
||
var characterName = name1; //set to user's name by default
|
||
} else { var characterName = mes.name }
|
||
|
||
var avatarImg = getUserAvatar(user_avatar);
|
||
const isSystem = mes.is_system;
|
||
const title = mes.title;
|
||
generatedPromptCache = "";
|
||
|
||
//for non-user mesages
|
||
if (!mes["is_user"]) {
|
||
if (mes.force_avatar) {
|
||
avatarImg = mes.force_avatar;
|
||
} else if (this_chid === undefined || this_chid === "invalid-safety-id") {
|
||
avatarImg = system_avatar;
|
||
} else {
|
||
if (characters[this_chid].avatar != "none") {
|
||
avatarImg = getThumbnailUrl('avatar', characters[this_chid].avatar);
|
||
} else {
|
||
avatarImg = default_avatar;
|
||
}
|
||
}
|
||
//old processing:
|
||
//if messge is from sytem, use the name provided in the message JSONL to proceed,
|
||
//if not system message, use name2 (char's name) to proceed
|
||
//characterName = mes.is_system || mes.force_avatar ? mes.name : name2;
|
||
} else if (mes["is_user"] && mes["force_avatar"]) {
|
||
// Special case for persona images.
|
||
avatarImg = mes["force_avatar"];
|
||
}
|
||
|
||
if (count_view_mes == 0) {
|
||
messageText = substituteParams(messageText);
|
||
}
|
||
messageText = messageFormatting(
|
||
messageText,
|
||
characterName,
|
||
isSystem,
|
||
mes.is_user,
|
||
);
|
||
const bias = messageFormatting(mes.extra?.bias ?? "");
|
||
let bookmarkLink = mes?.extra?.bookmark_link ?? '';
|
||
// Verify bookmarked chat still exists
|
||
// Cohee: Commented out for now. I'm worried of performance issues.
|
||
/*if (bookmarkLink !== '') {
|
||
let chat_names = selected_group
|
||
? getGroupChatNames(selected_group)
|
||
: Object.values(getPastCharacterChats()).map(({ file_name }) => file_name);
|
||
|
||
if (!chat_names.includes(bookmarkLink)) {
|
||
bookmarkLink = ''
|
||
}
|
||
}*/
|
||
let params = {
|
||
mesId: forceId ?? count_view_mes,
|
||
characterName: characterName,
|
||
isUser: mes.is_user,
|
||
avatarImg: avatarImg,
|
||
bias: bias,
|
||
isSystem: isSystem,
|
||
title: title,
|
||
bookmarkLink: bookmarkLink,
|
||
forceAvatar: mes.force_avatar,
|
||
timestamp: timestamp,
|
||
extra: mes.extra,
|
||
tokenCount: mes.extra?.token_count,
|
||
...formatGenerationTimer(mes.gen_started, mes.gen_finished, mes.extra?.token_count),
|
||
};
|
||
|
||
const HTMLForEachMes = getMessageFromTemplate(params);
|
||
|
||
if (type !== 'swipe') {
|
||
if (!insertAfter && !insertBefore) {
|
||
$("#chat").append(HTMLForEachMes);
|
||
}
|
||
else if (insertAfter) {
|
||
const target = $("#chat").find(`.mes[mesid="${insertAfter}"]`);
|
||
$(HTMLForEachMes).insertAfter(target);
|
||
$(HTMLForEachMes).find('.swipe_left').css('display', 'none');
|
||
$(HTMLForEachMes).find('.swipe_right').css('display', 'none');
|
||
} else {
|
||
const target = $("#chat").find(`.mes[mesid="${insertBefore}"]`);
|
||
$(HTMLForEachMes).insertBefore(target);
|
||
$(HTMLForEachMes).find('.swipe_left').css('display', 'none');
|
||
$(HTMLForEachMes).find('.swipe_right').css('display', 'none');
|
||
}
|
||
}
|
||
|
||
function getMessageId() {
|
||
if (typeof forceId == 'number') {
|
||
return forceId;
|
||
}
|
||
|
||
return type == 'swipe' ? count_view_mes - 1 : count_view_mes;
|
||
}
|
||
|
||
const newMessageId = getMessageId();
|
||
const newMessage = $(`#chat [mesid="${newMessageId}"]`);
|
||
const isSmallSys = mes?.extra?.isSmallSys;
|
||
newMessage.data("isSystem", isSystem);
|
||
|
||
if (isSystem) {
|
||
// newMessage.find(".mes_edit").hide();
|
||
newMessage.find(".mes_prompt").hide(); //don't need prompt button for sys
|
||
}
|
||
|
||
if (isSmallSys === true) {
|
||
newMessage.addClass('smallSysMes');
|
||
}
|
||
|
||
// 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 = type == 'swipe' ? params.mesId - 1 : params.mesId; //Number(newMessage.attr('mesId'));
|
||
|
||
//if we have itemized messages, and the array isn't null..
|
||
if (params.isUser === false && itemizedPrompts.length !== 0 && itemizedPrompts.length !== null) {
|
||
// console.log('looking through itemized prompts...');
|
||
//console.log(`mesIdToFind = ${mesIdToFind} from ${params.avatarImg}`);
|
||
//console.log(`itemizedPrompts.length = ${itemizedPrompts.length}`)
|
||
//console.log(itemizedPrompts);
|
||
|
||
for (var i = 0; i < itemizedPrompts.length; i++) {
|
||
//console.log(`itemized array item ${i} is MesID ${Number(itemizedPrompts[i].mesId)}, does it match ${Number(mesIdToFind)}?`);
|
||
if (Number(itemizedPrompts[i].mesId) === Number(mesIdToFind)) {
|
||
newMessage.find(".mes_prompt").show();
|
||
//console.log(`showing button for mesID ${params.mesId} from ${params.characterName}`);
|
||
break;
|
||
|
||
} /*else {
|
||
console.log(`no cache obj for mesID ${mesIdToFind}, hiding this prompt button`);
|
||
newMessage.find(".mes_prompt").hide();
|
||
console.log(itemizedPrompts);
|
||
} */
|
||
}
|
||
} else {
|
||
//console.log('itemizedprompt array empty null, or user, hiding this prompt buttons');
|
||
//$(".mes_prompt").hide();
|
||
newMessage.find(".mes_prompt").hide();
|
||
//console.log(itemizedPrompts);
|
||
}
|
||
|
||
newMessage.find('.avatar img').on('error', function () {
|
||
$(this).hide();
|
||
$(this).parent().html(`<div class="missing-avatar fa-solid fa-user-slash"></div>`);
|
||
});
|
||
|
||
if (type === 'swipe') {
|
||
const swipeMessage = $("#chat").find(`[mesid="${count_view_mes - 1}"]`);
|
||
swipeMessage.find('.mes_text').html('');
|
||
swipeMessage.find('.mes_text').append(messageText);
|
||
appendImageToMessage(mes, swipeMessage);
|
||
swipeMessage.attr('title', title);
|
||
swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`);
|
||
if (power_user.timestamp_model_icon && params.extra?.api) {
|
||
insertSVGIcon(swipeMessage, params.extra);
|
||
}
|
||
|
||
if (mes.swipe_id == mes.swipes.length - 1) {
|
||
swipeMessage.find('.mes_timer').text(params.timerValue);
|
||
swipeMessage.find('.mes_timer').attr('title', params.timerTitle);
|
||
swipeMessage.find('.tokenCounterDisplay').text(`${params.tokenCount}t`);
|
||
} else {
|
||
swipeMessage.find('.mes_timer').html('');
|
||
swipeMessage.find('.tokenCounterDisplay').html('');
|
||
}
|
||
} else if (typeof forceId == 'number') {
|
||
$("#chat").find(`[mesid="${forceId}"]`).find('.mes_text').append(messageText);
|
||
appendImageToMessage(mes, newMessage);
|
||
hideSwipeButtons();
|
||
showSwipeButtons();
|
||
} else {
|
||
$("#chat").find(`[mesid="${count_view_mes}"]`).find('.mes_text').append(messageText);
|
||
appendImageToMessage(mes, newMessage);
|
||
hideSwipeButtons();
|
||
count_view_mes++;
|
||
}
|
||
|
||
addCopyToCodeBlocks(newMessage);
|
||
|
||
// Don't scroll if not inserting last
|
||
if (!insertAfter && !insertBefore && scroll) {
|
||
$('#chat .mes').last().addClass('last_mes');
|
||
$('#chat .mes').eq(-2).removeClass('last_mes');
|
||
|
||
hideSwipeButtons();
|
||
showSwipeButtons();
|
||
scrollChatToBottom();
|
||
}
|
||
}
|
||
|
||
function getUserAvatar(avatarImg) {
|
||
return `User Avatars/${avatarImg}`;
|
||
}
|
||
|
||
/**
|
||
* Formats the title for the generation timer.
|
||
* @param {Date} gen_started Date when generation was started
|
||
* @param {Date} gen_finished Date when generation was finished
|
||
* @param {number} tokenCount Number of tokens generated (0 if not available)
|
||
* @returns {Object} Object containing the formatted timer value and title
|
||
* @example
|
||
* const { timerValue, timerTitle } = formatGenerationTimer(gen_started, gen_finished, tokenCount);
|
||
* console.log(timerValue); // 1.2s
|
||
* console.log(timerTitle); // Generation queued: 12:34:56 7 Jan 2021\nReply received: 12:34:57 7 Jan 2021\nTime to generate: 1.2 seconds\nToken rate: 5 t/s
|
||
*/
|
||
function formatGenerationTimer(gen_started, gen_finished, tokenCount) {
|
||
if (!gen_started || !gen_finished) {
|
||
return {};
|
||
}
|
||
|
||
const dateFormat = 'HH:mm:ss D MMM YYYY';
|
||
const start = moment(gen_started);
|
||
const finish = moment(gen_finished);
|
||
const seconds = finish.diff(start, 'seconds', true);
|
||
const timerValue = `${seconds.toFixed(1)}s`;
|
||
const timerTitle = [
|
||
`Generation queued: ${start.format(dateFormat)}`,
|
||
`Reply received: ${finish.format(dateFormat)}`,
|
||
`Time to generate: ${seconds} seconds`,
|
||
tokenCount > 0 ? `Token rate: ${Number(tokenCount / seconds).toFixed(1)} t/s` : '',
|
||
].join('\n');
|
||
|
||
return { timerValue, timerTitle };
|
||
}
|
||
|
||
function scrollChatToBottom() {
|
||
if (power_user.auto_scroll_chat_to_bottom) {
|
||
const chatElement = $("#chat");
|
||
let position = chatElement[0].scrollHeight;
|
||
|
||
if (power_user.waifuMode) {
|
||
const lastMessage = chatElement.find('.mes').last();
|
||
if (lastMessage.length) {
|
||
const lastMessagePosition = lastMessage.position().top;
|
||
position = chatElement.scrollTop() + lastMessagePosition;
|
||
}
|
||
}
|
||
|
||
chatElement.scrollTop(position);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Substitutes {{macro}} parameters in a string.
|
||
* @param {string} content - The string to substitute parameters in.
|
||
* @param {*} _name1 - The name of the user. Uses global name1 if not provided.
|
||
* @param {*} _name2 - The name of the character. Uses global name2 if not provided.
|
||
* @param {*} _original - The original message for {{original}} substitution.
|
||
* @param {*} _group - The group members list for {{group}} substitution.
|
||
* @returns {string} The string with substituted parameters.
|
||
*/
|
||
function substituteParams(content, _name1, _name2, _original, _group) {
|
||
_name1 = _name1 ?? name1;
|
||
_name2 = _name2 ?? name2;
|
||
_group = _group ?? name2;
|
||
|
||
if (!content) {
|
||
return '';
|
||
}
|
||
|
||
// Replace {{original}} with the original message
|
||
// Note: only replace the first instance of {{original}}
|
||
// This will hopefully prevent the abuse
|
||
if (typeof _original === 'string') {
|
||
content = content.replace(/{{original}}/i, _original);
|
||
}
|
||
content = content.replace(/{{input}}/gi, $('#send_textarea').val());
|
||
content = content.replace(/{{user}}/gi, _name1);
|
||
content = content.replace(/{{char}}/gi, _name2);
|
||
content = content.replace(/{{charIfNotGroup}}/gi, _group);
|
||
content = content.replace(/{{group}}/gi, _group);
|
||
|
||
content = content.replace(/<USER>/gi, _name1);
|
||
content = content.replace(/<BOT>/gi, _name2);
|
||
content = content.replace(/<CHARIFNOTGROUP>/gi, _group);
|
||
content = content.replace(/<GROUP>/gi, _group);
|
||
|
||
content = content.replace(/{{time}}/gi, moment().format('LT'));
|
||
content = content.replace(/{{date}}/gi, moment().format('LL'));
|
||
content = content.replace(/{{idle_duration}}/gi, () => getTimeSinceLastMessage());
|
||
content = content.replace(/{{time_UTC([-+]\d+)}}/gi, (_, offset) => {
|
||
const utcOffset = parseInt(offset, 10);
|
||
const utcTime = moment().utc().utcOffset(utcOffset).format('LT');
|
||
return utcTime;
|
||
});
|
||
content = randomReplace(content);
|
||
content = diceRollReplace(content);
|
||
return content;
|
||
}
|
||
|
||
function getTimeSinceLastMessage() {
|
||
const now = moment();
|
||
|
||
if (Array.isArray(chat) && chat.length > 0) {
|
||
let lastMessage;
|
||
let takeNext = false;
|
||
|
||
for (let i = chat.length - 1; i >= 0; i--) {
|
||
const message = chat[i];
|
||
|
||
if (message.is_system) {
|
||
continue;
|
||
}
|
||
|
||
if (message.is_user && takeNext) {
|
||
lastMessage = message;
|
||
break;
|
||
}
|
||
|
||
takeNext = true;
|
||
}
|
||
|
||
if (lastMessage?.send_date) {
|
||
const lastMessageDate = timestampToMoment(lastMessage.send_date);
|
||
const duration = moment.duration(now.diff(lastMessageDate));
|
||
return duration.humanize();
|
||
}
|
||
}
|
||
|
||
return 'just now';
|
||
}
|
||
|
||
function randomReplace(input, emptyListPlaceholder = '') {
|
||
const randomPattern = /{{random[ : ]([^}]+)}}/gi;
|
||
|
||
return input.replace(randomPattern, (match, listString) => {
|
||
const list = listString.split(',').map(item => item.trim()).filter(item => item.length > 0);
|
||
|
||
if (list.length === 0) {
|
||
return emptyListPlaceholder;
|
||
}
|
||
|
||
var rng = new Math.seedrandom('added entropy.', { entropy: true });
|
||
const randomIndex = Math.floor(rng() * list.length);
|
||
|
||
//const randomIndex = Math.floor(Math.random() * list.length);
|
||
return list[randomIndex];
|
||
});
|
||
}
|
||
|
||
function diceRollReplace(input, invalidRollPlaceholder = '') {
|
||
const rollPattern = /{{roll[ : ]([^}]+)}}/gi;
|
||
|
||
return input.replace(rollPattern, (match, matchValue) => {
|
||
let formula = matchValue.trim();
|
||
|
||
if (isDigitsOnly(formula)) {
|
||
formula = `1d${formula}`;
|
||
}
|
||
|
||
const isValid = droll.validate(formula);
|
||
|
||
if (!isValid) {
|
||
console.debug(`Invalid roll formula: ${formula}`);
|
||
return invalidRollPlaceholder;
|
||
}
|
||
|
||
const result = droll.roll(formula);
|
||
return new String(result.total);
|
||
});
|
||
}
|
||
|
||
function getStoppingStrings(isImpersonate) {
|
||
const charString = `\n${name2}:`;
|
||
const userString = `\n${name1}:`;
|
||
const result = isImpersonate ? [charString] : [userString];
|
||
|
||
result.push(userString);
|
||
|
||
// Add other group members as the stopping strings
|
||
if (selected_group) {
|
||
const group = groups.find(x => x.id === selected_group);
|
||
|
||
if (group && Array.isArray(group.members)) {
|
||
const names = group.members
|
||
.map(x => characters.find(y => y.avatar == x))
|
||
.filter(x => x && x.name && x.name !== name2)
|
||
.map(x => `\n${x.name}:`);
|
||
result.push(...names);
|
||
}
|
||
}
|
||
|
||
result.push(...getInstructStoppingSequences());
|
||
|
||
if (power_user.custom_stopping_strings) {
|
||
const customStoppingStrings = getCustomStoppingStrings();
|
||
result.push(...customStoppingStrings);
|
||
}
|
||
|
||
return result.filter(onlyUnique);
|
||
}
|
||
|
||
/**
|
||
* Background generation based on the provided prompt.
|
||
* @param {string} quiet_prompt Instruction prompt for the AI
|
||
* @param {boolean} quietToLoud Whether a message should be sent in a foreground (loud) or background (quiet) mode
|
||
* @returns
|
||
*/
|
||
export async function generateQuietPrompt(quiet_prompt, quietToLoud) {
|
||
return await new Promise(
|
||
async function promptPromise(resolve, reject) {
|
||
if (quietToLoud === true) {
|
||
try {
|
||
await Generate('quiet', { resolve, reject, quiet_prompt, quietToLoud: true, force_name2: true, });
|
||
}
|
||
catch {
|
||
reject();
|
||
}
|
||
}
|
||
else {
|
||
try {
|
||
await Generate('quiet', { resolve, reject, quiet_prompt, quietToLoud: false, force_name2: true, });
|
||
}
|
||
catch {
|
||
reject();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function processCommands(message, type) {
|
||
if (type == "regenerate" || type == "swipe" || type == 'quiet') {
|
||
return null;
|
||
}
|
||
|
||
const result = executeSlashCommands(message);
|
||
$("#send_textarea").val(result.newText).trigger('input');
|
||
|
||
// interrupt generation if the input was nothing but a command
|
||
if (message.length > 0 && result.newText.length === 0) {
|
||
return true;
|
||
}
|
||
|
||
return result.interrupt;
|
||
}
|
||
|
||
function sendSystemMessage(type, text, extra = {}) {
|
||
const systemMessage = system_messages[type];
|
||
|
||
if (!systemMessage) {
|
||
return;
|
||
}
|
||
|
||
const newMessage = { ...systemMessage, send_date: getMessageTimeStamp() };
|
||
|
||
if (text) {
|
||
newMessage.mes = text;
|
||
}
|
||
|
||
if (type == system_message_types.SLASH_COMMANDS) {
|
||
newMessage.mes = getSlashCommandsHelp();
|
||
}
|
||
|
||
if (!newMessage.extra) {
|
||
newMessage.extra = {};
|
||
}
|
||
|
||
newMessage.extra = Object.assign(newMessage.extra, extra);
|
||
newMessage.extra.type = type;
|
||
|
||
chat.push(newMessage);
|
||
addOneMessage(newMessage);
|
||
is_send_press = false;
|
||
}
|
||
|
||
export function extractMessageBias(message) {
|
||
if (!message) {
|
||
return null;
|
||
}
|
||
|
||
try {
|
||
const biasHandlebars = Handlebars.create();
|
||
const biasMatches = [];
|
||
biasHandlebars.registerHelper('bias', function (text) {
|
||
biasMatches.push(text);
|
||
return '';
|
||
});
|
||
const template = biasHandlebars.compile(message);
|
||
template({});
|
||
|
||
if (biasMatches && biasMatches.length > 0) {
|
||
return ` ${biasMatches.join(" ")}`;
|
||
}
|
||
|
||
return '';
|
||
} catch {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Removes impersonated group member lines from the group member messages.
|
||
* Doesn't do anything if group reply trimming is disabled.
|
||
* @param {string} getMessage Group message
|
||
* @returns Cleaned-up group message
|
||
*/
|
||
function cleanGroupMessage(getMessage) {
|
||
if (power_user.disable_group_trimming) {
|
||
return getMessage;
|
||
}
|
||
|
||
const group = groups.find((x) => x.id == selected_group);
|
||
|
||
if (group && Array.isArray(group.members) && group.members) {
|
||
for (let member of group.members) {
|
||
const character = characters.find(x => x.avatar == member);
|
||
|
||
if (!character) {
|
||
continue;
|
||
}
|
||
|
||
const name = character.name;
|
||
|
||
// Skip current speaker.
|
||
if (name === name2) {
|
||
continue;
|
||
}
|
||
|
||
const regex = new RegExp(`(^|\n)${escapeRegex(name)}:`);
|
||
const nameMatch = getMessage.match(regex);
|
||
if (nameMatch) {
|
||
getMessage = getMessage.substring(0, nameMatch.index);
|
||
}
|
||
}
|
||
}
|
||
return getMessage;
|
||
}
|
||
|
||
function addPersonaDescriptionExtensionPrompt() {
|
||
if (!power_user.persona_description) {
|
||
return;
|
||
}
|
||
|
||
const promptPositions = [persona_description_positions.BOTTOM_AN, persona_description_positions.TOP_AN];
|
||
|
||
if (promptPositions.includes(power_user.persona_description_position) && shouldWIAddPrompt) {
|
||
const originalAN = extension_prompts[NOTE_MODULE_NAME].value
|
||
const ANWithDesc = power_user.persona_description_position === persona_description_positions.TOP_AN
|
||
? `${power_user.persona_description}\n${originalAN}`
|
||
: `${originalAN}\n${power_user.persona_description}`;
|
||
|
||
setExtensionPrompt(NOTE_MODULE_NAME, ANWithDesc, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
|
||
}
|
||
}
|
||
|
||
function getAllExtensionPrompts() {
|
||
const value = Object
|
||
.values(extension_prompts)
|
||
.filter(x => x.value)
|
||
.map(x => x.value.trim())
|
||
.join('\n');
|
||
|
||
return value.length ? substituteParams(value) : '';
|
||
}
|
||
|
||
function getExtensionPrompt(position = 0, depth = undefined, separator = "\n") {
|
||
let extension_prompt = Object.keys(extension_prompts)
|
||
.sort()
|
||
.map((x) => extension_prompts[x])
|
||
.filter(x => x.position == position && x.value && (depth === undefined || x.depth == depth))
|
||
.map(x => x.value.trim())
|
||
.join(separator);
|
||
if (extension_prompt.length && !extension_prompt.startsWith(separator)) {
|
||
extension_prompt = separator + extension_prompt;
|
||
}
|
||
if (extension_prompt.length && !extension_prompt.endsWith(separator)) {
|
||
extension_prompt = extension_prompt + separator;
|
||
}
|
||
if (extension_prompt.length) {
|
||
extension_prompt = substituteParams(extension_prompt);
|
||
}
|
||
return extension_prompt;
|
||
}
|
||
|
||
function baseChatReplace(value, name1, name2) {
|
||
if (value !== undefined && value.length > 0) {
|
||
value = substituteParams(value, name1, name2);
|
||
|
||
if (power_user.collapse_newlines) {
|
||
value = collapseNewlines(value);
|
||
}
|
||
|
||
value = value.replace(/\r/g, '');
|
||
}
|
||
return value;
|
||
}
|
||
|
||
function isStreamingEnabled() {
|
||
const noStreamSources = [chat_completion_sources.SCALE, chat_completion_sources.AI21, chat_completion_sources.PALM];
|
||
return ((main_api == 'openai' && oai_settings.stream_openai && !noStreamSources.includes(oai_settings.chat_completion_source))
|
||
|| (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming)
|
||
|| (main_api == 'novel' && nai_settings.streaming_novel)
|
||
|| (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming));
|
||
}
|
||
|
||
function showStopButton() {
|
||
$('#mes_stop').css({ 'display': 'flex' });
|
||
}
|
||
|
||
function hideStopButton() {
|
||
$('#mes_stop').css({ 'display': 'none' });
|
||
}
|
||
|
||
class StreamingProcessor {
|
||
showMessageButtons(messageId) {
|
||
if (messageId == -1) {
|
||
return;
|
||
}
|
||
|
||
showStopButton();
|
||
$(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'none' });
|
||
}
|
||
|
||
hideMessageButtons(messageId) {
|
||
if (messageId == -1) {
|
||
return;
|
||
}
|
||
|
||
hideStopButton();
|
||
$(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'flex' });
|
||
}
|
||
|
||
async onStartStreaming(text) {
|
||
let messageId = -1;
|
||
|
||
if (this.type == "impersonate") {
|
||
$('#send_textarea').val('').trigger('input');
|
||
}
|
||
else {
|
||
await saveReply(this.type, text);
|
||
messageId = count_view_mes - 1;
|
||
this.showMessageButtons(messageId);
|
||
}
|
||
|
||
hideSwipeButtons();
|
||
scrollChatToBottom();
|
||
return messageId;
|
||
}
|
||
|
||
removePrefix(text) {
|
||
const name1Marker = `${name1}: `;
|
||
const name2Marker = `${name2}: `;
|
||
|
||
if (text) {
|
||
if (text.startsWith(name1Marker)) {
|
||
text = text.replace(name1Marker, '');
|
||
}
|
||
if (text.startsWith(name2Marker)) {
|
||
text = text.replace(name2Marker, '');
|
||
}
|
||
}
|
||
return text;
|
||
}
|
||
|
||
onProgressStreaming(messageId, text, isFinal) {
|
||
const isImpersonate = this.type == "impersonate";
|
||
const isContinue = this.type == "continue";
|
||
text = this.removePrefix(text);
|
||
let processedText = cleanUpMessage(text, isImpersonate, isContinue, !isFinal);
|
||
|
||
// Predict unbalanced asterisks / quotes during streaming
|
||
const charsToBalance = ['*', '"'];
|
||
for (const char of charsToBalance) {
|
||
if (!isFinal && isOdd(countOccurrences(processedText, char))) {
|
||
// Add character at the end to balance it
|
||
processedText = processedText.trimEnd() + char;
|
||
}
|
||
}
|
||
|
||
if (isImpersonate) {
|
||
$('#send_textarea').val(processedText).trigger('input');
|
||
}
|
||
else {
|
||
let currentTime = new Date();
|
||
// Don't waste time calculating token count for streaming
|
||
let currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(processedText, 0) : 0;
|
||
const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount);
|
||
chat[messageId]['mes'] = processedText;
|
||
chat[messageId]['gen_started'] = this.timeStarted;
|
||
chat[messageId]['gen_finished'] = currentTime;
|
||
|
||
if (currentTokenCount) {
|
||
if (!chat[messageId]['extra']) {
|
||
chat[messageId]['extra'] = {};
|
||
}
|
||
|
||
chat[messageId]['extra']['token_count'] = currentTokenCount;
|
||
const tokenCounter = $(`#chat .mes[mesid="${messageId}"] .tokenCounterDisplay`);
|
||
tokenCounter.text(`${currentTokenCount}t`);
|
||
}
|
||
|
||
if ((this.type == 'swipe' || this.type === 'continue') && Array.isArray(chat[messageId]['swipes'])) {
|
||
chat[messageId]['swipes'][chat[messageId]['swipe_id']] = processedText;
|
||
chat[messageId]['swipe_info'][chat[messageId]['swipe_id']] = { 'send_date': chat[messageId]['send_date'], 'gen_started': chat[messageId]['gen_started'], 'gen_finished': chat[messageId]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])) };
|
||
}
|
||
|
||
let formattedText = messageFormatting(
|
||
processedText,
|
||
chat[messageId].name,
|
||
chat[messageId].is_system,
|
||
chat[messageId].is_user,
|
||
);
|
||
const mesText = $(`#chat .mes[mesid="${messageId}"] .mes_text`);
|
||
mesText.html(formattedText);
|
||
$(`#chat .mes[mesid="${messageId}"] .mes_timer`).text(timePassed.timerValue).attr('title', timePassed.timerTitle);
|
||
this.setFirstSwipe(messageId);
|
||
}
|
||
|
||
if (!scrollLock) {
|
||
scrollChatToBottom();
|
||
}
|
||
}
|
||
|
||
async onFinishStreaming(messageId, text) {
|
||
this.hideMessageButtons(this.messageId);
|
||
this.onProgressStreaming(messageId, text, true);
|
||
addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`));
|
||
|
||
if (this.type !== 'impersonate') {
|
||
await eventSource.emit(event_types.MESSAGE_RECEIVED, this.messageId);
|
||
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, this.messageId);
|
||
} else {
|
||
await eventSource.emit(event_types.IMPERSONATE_READY, text);
|
||
}
|
||
|
||
await saveChatConditional();
|
||
activateSendButtons();
|
||
showSwipeButtons();
|
||
setGenerationProgress(0);
|
||
generatedPromptCache = '';
|
||
|
||
//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() {
|
||
this.hideMessageButtons(this.messageId);
|
||
$("#send_textarea").removeAttr('disabled');
|
||
is_send_press = false;
|
||
activateSendButtons();
|
||
setGenerationProgress(0);
|
||
showSwipeButtons();
|
||
}
|
||
|
||
setFirstSwipe(messageId) {
|
||
if (this.type !== 'swipe' && this.type !== 'impersonate') {
|
||
if (Array.isArray(chat[messageId]['swipes']) && chat[messageId]['swipes'].length === 1 && chat[messageId]['swipe_id'] === 0) {
|
||
chat[messageId]['swipes'][0] = chat[messageId]['mes'];
|
||
chat[messageId]['swipe_info'][0] = { 'send_date': chat[messageId]['send_date'], 'gen_started': chat[messageId]['gen_started'], 'gen_finished': chat[messageId]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])) };
|
||
}
|
||
}
|
||
}
|
||
|
||
onStopStreaming() {
|
||
this.onErrorStreaming();
|
||
}
|
||
|
||
*nullStreamingGeneration() {
|
||
throw new Error('Generation function for streaming is not hooked up');
|
||
}
|
||
|
||
constructor(type, force_name2, timeStarted) {
|
||
this.result = "";
|
||
this.messageId = -1;
|
||
this.type = type;
|
||
this.force_name2 = force_name2;
|
||
this.isStopped = false;
|
||
this.isFinished = false;
|
||
this.generator = this.nullStreamingGeneration;
|
||
this.abortController = new AbortController();
|
||
this.firstMessageText = '...';
|
||
this.timeStarted = timeStarted;
|
||
}
|
||
|
||
async generate() {
|
||
if (this.messageId == -1) {
|
||
this.messageId = await this.onStartStreaming(this.firstMessageText);
|
||
await delay(1); // delay for message to be rendered
|
||
scrollLock = false;
|
||
}
|
||
|
||
try {
|
||
for await (const text of this.generator()) {
|
||
if (this.isStopped) {
|
||
this.onStopStreaming();
|
||
return;
|
||
}
|
||
|
||
this.result = text;
|
||
this.onProgressStreaming(this.messageId, message_already_generated + text);
|
||
}
|
||
}
|
||
catch (err) {
|
||
console.error(err);
|
||
this.onErrorStreaming();
|
||
this.isStopped = true;
|
||
return;
|
||
}
|
||
|
||
this.isFinished = true;
|
||
return this.result;
|
||
}
|
||
}
|
||
|
||
async function Generate(type, { automatic_trigger, force_name2, resolve, reject, quiet_prompt, quietToLoud, force_chid, signal } = {}, dryRun = false) {
|
||
//console.log('Generate entered');
|
||
setGenerationProgress(0);
|
||
generation_started = new Date();
|
||
|
||
// Don't recreate abort controller if signal is passed
|
||
if (!(abortController && signal)) {
|
||
abortController = new AbortController();
|
||
}
|
||
|
||
// OpenAI doesn't need instruct mode. Use OAI main prompt instead.
|
||
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
|
||
const isImpersonate = type == "impersonate";
|
||
|
||
message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
|
||
|
||
const interruptedByCommand = processCommands($("#send_textarea").val(), type);
|
||
|
||
if (interruptedByCommand) {
|
||
$("#send_textarea").val('').trigger('input');
|
||
is_send_press = false;
|
||
return;
|
||
}
|
||
|
||
if (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming && !textgenerationwebui_settings.streaming_url) {
|
||
toastr.error('Streaming URL is not set. Look it up in the console window when starting TextGen Web UI');
|
||
is_send_press = false;
|
||
return;
|
||
}
|
||
|
||
if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) {
|
||
toastr.error('Streaming is enabled, but the version of Kobold used does not support token streaming.', undefined, { timeOut: 10000, preventDuplicates: true, });
|
||
is_send_press = false;
|
||
return;
|
||
}
|
||
|
||
if (isHordeGenerationNotAllowed()) {
|
||
is_send_press = false;
|
||
return;
|
||
}
|
||
|
||
// Hide swipes if not in a dry run.
|
||
if (!dryRun) {
|
||
hideSwipeButtons();
|
||
}
|
||
|
||
// Set empty promise resolution functions
|
||
if (typeof resolve !== 'function') {
|
||
resolve = () => { };
|
||
}
|
||
if (typeof reject !== 'function') {
|
||
reject = () => { };
|
||
}
|
||
|
||
if (selected_group && !is_group_generating && !dryRun) {
|
||
generateGroupWrapper(false, type, { resolve, reject, quiet_prompt, force_chid, signal: abortController.signal });
|
||
return;
|
||
} else if (selected_group && !is_group_generating && dryRun) {
|
||
const characterIndexMap = new Map(characters.map((char, index) => [char.avatar, index]));
|
||
const group = groups.find((x) => x.id === selected_group);
|
||
|
||
const enabledMembers = group.members.reduce((acc, member) => {
|
||
if (!group.disabled_members.includes(member) && !acc.includes(member)) {
|
||
acc.push(member);
|
||
}
|
||
return acc;
|
||
}, []);
|
||
|
||
const memberIds = enabledMembers
|
||
.map((member) => characterIndexMap.get(member))
|
||
.filter((index) => index !== undefined && index !== null);
|
||
|
||
if (memberIds.length > 0) {
|
||
setCharacterId(memberIds[0]);
|
||
setCharacterName('');
|
||
} else {
|
||
console.log('No enabled members found');
|
||
is_send_press = false;
|
||
return;
|
||
}
|
||
}
|
||
|
||
//#########QUIET PROMPT STUFF##############
|
||
//this function just gives special care to novel quiet instruction prompts
|
||
if (quiet_prompt) {
|
||
quiet_prompt = substituteParams(quiet_prompt);
|
||
quiet_prompt = main_api == 'novel' && !quietToLoud ? adjustNovelInstructionPrompt(quiet_prompt) : quiet_prompt;
|
||
}
|
||
|
||
if (true === dryRun ||
|
||
(online_status != 'no_connection' && this_chid != undefined && this_chid !== 'invalid-safety-id')) {
|
||
let textareaText;
|
||
if (type !== 'regenerate' && type !== "swipe" && type !== 'quiet' && !isImpersonate && !dryRun) {
|
||
is_send_press = true;
|
||
textareaText = $("#send_textarea").val();
|
||
$("#send_textarea").val('').trigger('input');
|
||
} else {
|
||
textareaText = "";
|
||
if (chat.length && chat[chat.length - 1]['is_user']) {
|
||
//do nothing? why does this check exist?
|
||
}
|
||
else if (type !== 'quiet' && type !== "swipe" && !isImpersonate && !dryRun && chat.length) {
|
||
chat.length = chat.length - 1;
|
||
count_view_mes -= 1;
|
||
$('#chat').children().last().hide(250, function () {
|
||
$(this).remove();
|
||
});
|
||
await eventSource.emit(event_types.MESSAGE_DELETED, chat.length);
|
||
}
|
||
}
|
||
|
||
if (!type && !textareaText && power_user.continue_on_send && !selected_group && chat.length && !chat[chat.length - 1]['is_user'] && !chat[chat.length - 1]['is_system']) {
|
||
type = 'continue';
|
||
}
|
||
|
||
const isContinue = type == 'continue';
|
||
|
||
// Rewrite the generation timer to account for the time passed for all the continuations.
|
||
if (isContinue && chat.length) {
|
||
const prevFinished = chat[chat.length - 1]['gen_finished'];
|
||
const prevStarted = chat[chat.length - 1]['gen_started'];
|
||
|
||
if (prevFinished && prevStarted) {
|
||
const timePassed = prevFinished - prevStarted;
|
||
generation_started = new Date(Date.now() - timePassed);
|
||
chat[chat.length - 1]['gen_started'] = generation_started;
|
||
}
|
||
}
|
||
|
||
if (!dryRun) {
|
||
deactivateSendButtons();
|
||
}
|
||
|
||
let { messageBias, promptBias, isUserPromptBias } = getBiasStrings(textareaText, type);
|
||
|
||
//*********************************
|
||
//PRE FORMATING STRING
|
||
//*********************************
|
||
|
||
//for normal messages sent from user..
|
||
if (textareaText != "" && !automatic_trigger && type !== 'quiet') {
|
||
// If user message contains no text other than bias - send as a system message
|
||
if (messageBias && replaceBiasMarkup(textareaText).trim().length === 0) {
|
||
sendSystemMessage(system_message_types.GENERIC, ' ', { bias: messageBias });
|
||
}
|
||
else {
|
||
await sendMessageAsUser(textareaText, messageBias);
|
||
}
|
||
}
|
||
else if (textareaText == "" && !automatic_trigger && type === undefined && main_api == 'openai' && oai_settings.send_if_empty.trim().length > 0) {
|
||
// Use send_if_empty if set and the user message is empty. Only when sending messages normally
|
||
await sendMessageAsUser(oai_settings.send_if_empty.trim(), messageBias);
|
||
}
|
||
|
||
////////////////////////////////////
|
||
const scenarioText = chat_metadata['scenario'] || characters[this_chid].scenario;
|
||
let charDescription = baseChatReplace(characters[this_chid].description.trim(), name1, name2);
|
||
let charPersonality = baseChatReplace(characters[this_chid].personality.trim(), name1, name2);
|
||
let personaDescription = baseChatReplace(power_user.persona_description.trim(), name1, name2);
|
||
let Scenario = baseChatReplace(scenarioText.trim(), name1, name2);
|
||
let mesExamples = baseChatReplace(characters[this_chid].mes_example.trim(), name1, name2);
|
||
let systemPrompt = power_user.prefer_character_prompt ? baseChatReplace(characters[this_chid].data?.system_prompt?.trim(), name1, name2) : '';
|
||
let jailbreakPrompt = power_user.prefer_character_jailbreak ? baseChatReplace(characters[this_chid].data?.post_history_instructions?.trim(), name1, name2) : '';
|
||
|
||
if (isInstruct) {
|
||
systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : baseChatReplace(power_user.instruct.system_prompt, name1, name2);
|
||
systemPrompt = formatInstructModeSystemPrompt(substituteParams(systemPrompt, name1, name2, power_user.instruct.system_prompt));
|
||
}
|
||
|
||
// Parse example messages
|
||
if (!mesExamples.startsWith('<START>')) {
|
||
mesExamples = '<START>\n' + mesExamples.trim();
|
||
}
|
||
if (mesExamples.replace(/<START>/gi, '').trim().length === 0) {
|
||
mesExamples = '';
|
||
}
|
||
if (mesExamples && isInstruct) {
|
||
mesExamples = formatInstructModeExamples(mesExamples, name1, name2)
|
||
}
|
||
|
||
const exampleSeparator = power_user.context.example_separator ? `${substituteParams(power_user.context.example_separator)}\n` : '';
|
||
const blockHeading = main_api === 'openai' ? '<START>\n' : exampleSeparator;
|
||
let mesExamplesArray = mesExamples.split(/<START>/gi).slice(1).map(block => `${blockHeading}${block.trim()}\n`);
|
||
|
||
if (power_user.strip_examples)
|
||
mesExamplesArray = []
|
||
|
||
// First message in fresh 1-on-1 chat reacts to user/character settings changes
|
||
if (chat.length) {
|
||
chat[0].mes = substituteParams(chat[0].mes);
|
||
}
|
||
|
||
// Collect messages with usable content
|
||
let coreChat = chat.filter(x => !x.is_system);
|
||
if (type === 'swipe') {
|
||
coreChat.pop();
|
||
}
|
||
|
||
// Determine token limit
|
||
let this_max_context = getMaxContextSize();
|
||
|
||
if (!dryRun) {
|
||
console.debug('Running extension interceptors');
|
||
await runGenerationInterceptors(coreChat, this_max_context);
|
||
} else {
|
||
console.debug('Skipping extension interceptors for dry run');
|
||
}
|
||
|
||
console.log(`Core/all messages: ${coreChat.length}/${chat.length}`);
|
||
|
||
// kingbri MARK: - Make sure the prompt bias isn't the same as the user bias
|
||
if ((promptBias && !isUserPromptBias) || power_user.always_force_name2 || main_api == 'novel') {
|
||
force_name2 = true;
|
||
}
|
||
|
||
if (isImpersonate) {
|
||
force_name2 = false;
|
||
}
|
||
|
||
//////////////////////////////////
|
||
|
||
let chat2 = [];
|
||
let continue_mag = '';
|
||
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
|
||
// For OpenAI it's only used in WI
|
||
if (main_api == 'openai' && (!world_info || world_info.length === 0)) {
|
||
console.debug('No WI, skipping chat2 for OAI');
|
||
break;
|
||
}
|
||
|
||
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, false);
|
||
|
||
if (j === 0 && isInstruct) {
|
||
// Reformat with the first output sequence (if any)
|
||
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST);
|
||
}
|
||
|
||
// Do not suffix the message for continuation
|
||
if (i === 0 && isContinue) {
|
||
if (isInstruct) {
|
||
// Reformat with the last output sequence (if any)
|
||
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST);
|
||
}
|
||
|
||
chat2[i] = chat2[i].slice(0, chat2[i].lastIndexOf(coreChat[j].mes) + coreChat[j].mes.length);
|
||
continue_mag = coreChat[j].mes;
|
||
}
|
||
}
|
||
|
||
// Adjust token limit for Horde
|
||
let adjustedParams;
|
||
if (main_api == 'koboldhorde' && (horde_settings.auto_adjust_context_length || horde_settings.auto_adjust_response_length)) {
|
||
try {
|
||
adjustedParams = await adjustHordeGenerationParams(max_context, amount_gen);
|
||
}
|
||
catch {
|
||
activateSendButtons();
|
||
return;
|
||
}
|
||
if (horde_settings.auto_adjust_context_length) {
|
||
this_max_context = (adjustedParams.maxContextLength - adjustedParams.maxLength);
|
||
}
|
||
}
|
||
|
||
// Extension added strings
|
||
// Set non-WI AN
|
||
setFloatingPrompt();
|
||
// Add WI to prompt (and also inject WI to AN value via hijack)
|
||
let { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoDepth } = await getWorldInfoPrompt(chat2, this_max_context);
|
||
// Add persona description to prompt
|
||
addPersonaDescriptionExtensionPrompt();
|
||
// Call combined AN into Generate
|
||
let allAnchors = getAllExtensionPrompts();
|
||
const beforeScenarioAnchor = getExtensionPrompt(extension_prompt_types.BEFORE_PROMPT).trimStart();
|
||
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.IN_PROMPT);
|
||
let zeroDepthAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, 0, ' ');
|
||
|
||
const storyStringParams = {
|
||
description: charDescription,
|
||
personality: charPersonality,
|
||
persona: personaDescription,
|
||
scenario: Scenario,
|
||
system: isInstruct ? systemPrompt : '',
|
||
char: name2,
|
||
user: name1,
|
||
wiBefore: worldInfoBefore,
|
||
wiAfter: worldInfoAfter,
|
||
loreBefore: worldInfoBefore,
|
||
loreAfter: worldInfoAfter,
|
||
};
|
||
|
||
const storyString = renderStoryString(storyStringParams);
|
||
|
||
// Add all depth WI entries to prompt
|
||
if (Array.isArray(worldInfoDepth)) {
|
||
worldInfoDepth.forEach((e) => {
|
||
const joinedEntries = e.entries.join("\n");
|
||
setExtensionPrompt(`customDepthWI-${e.depth}`, joinedEntries, extension_prompt_types.IN_CHAT, e.depth)
|
||
});
|
||
}
|
||
|
||
if (main_api === 'openai') {
|
||
message_already_generated = '';
|
||
setOpenAIMessages(coreChat);
|
||
setOpenAIMessageExamples(mesExamplesArray);
|
||
}
|
||
|
||
// hack for regeneration of the first message
|
||
if (chat2.length == 0) {
|
||
chat2.push('');
|
||
}
|
||
|
||
let examplesString = '';
|
||
let chatString = '';
|
||
let cyclePrompt = '';
|
||
|
||
function getMessagesTokenCount() {
|
||
const encodeString = [
|
||
storyString,
|
||
examplesString,
|
||
chatString,
|
||
allAnchors,
|
||
quiet_prompt,
|
||
cyclePrompt,
|
||
].join('').replace(/\r/gm, '');
|
||
return getTokenCount(encodeString, power_user.token_padding);
|
||
}
|
||
|
||
// Force pinned examples into the context
|
||
let pinExmString;
|
||
if (power_user.pin_examples) {
|
||
pinExmString = examplesString = mesExamplesArray.join('');
|
||
}
|
||
|
||
if (isContinue) {
|
||
cyclePrompt = chat2.shift();
|
||
}
|
||
|
||
// Collect enough messages to fill the context
|
||
let arrMes = [];
|
||
let tokenCount = getMessagesTokenCount();
|
||
for (let item of chat2) {
|
||
// not needed for OAI prompting
|
||
if (main_api == 'openai') {
|
||
break;
|
||
}
|
||
|
||
tokenCount += getTokenCount(item.replace(/\r/gm, ''))
|
||
chatString = item + chatString;
|
||
if (tokenCount < this_max_context) {
|
||
arrMes[arrMes.length] = item;
|
||
} else {
|
||
break;
|
||
}
|
||
|
||
// Prevent UI thread lock on tokenization
|
||
await delay(1);
|
||
}
|
||
|
||
if (main_api !== 'openai') {
|
||
setInContextMessages(arrMes.length, type);
|
||
}
|
||
|
||
// Estimate how many unpinned example messages fit in the context
|
||
tokenCount = getMessagesTokenCount();
|
||
let count_exm_add = 0;
|
||
if (!power_user.pin_examples) {
|
||
for (let example of mesExamplesArray) {
|
||
tokenCount += getTokenCount(example.replace(/\r/gm, ''))
|
||
examplesString += example;
|
||
if (tokenCount < this_max_context) {
|
||
count_exm_add++;
|
||
} else {
|
||
break;
|
||
}
|
||
await delay(1);
|
||
}
|
||
}
|
||
|
||
let mesSend = [];
|
||
console.debug('calling runGenerate');
|
||
|
||
if (!dryRun) {
|
||
streamingProcessor = isStreamingEnabled() ? new StreamingProcessor(type, force_name2, generation_started) : false;
|
||
}
|
||
|
||
if (isContinue) {
|
||
// Coping mechanism for OAI spacing
|
||
if ((main_api === 'openai') && !cyclePrompt.endsWith(' ')) {
|
||
cyclePrompt += ' ';
|
||
continue_mag += ' ';
|
||
}
|
||
|
||
// Save reply does add cycle text to the prompt, so it's not needed here
|
||
streamingProcessor && (streamingProcessor.firstMessageText = '');
|
||
message_already_generated = continue_mag;
|
||
}
|
||
|
||
const originalType = type;
|
||
runGenerate(cyclePrompt);
|
||
|
||
async function runGenerate(cycleGenerationPrompt = '') {
|
||
if (!dryRun) {
|
||
is_send_press = true;
|
||
}
|
||
|
||
generatedPromptCache += cycleGenerationPrompt;
|
||
if (generatedPromptCache.length == 0 || type === 'continue') {
|
||
if (main_api === 'openai') {
|
||
generateOpenAIPromptCache();
|
||
}
|
||
|
||
console.debug('generating prompt');
|
||
chatString = "";
|
||
arrMes = arrMes.reverse();
|
||
arrMes.forEach(function (item, i, arr) {// For added anchors and others
|
||
// OAI doesn't need all of this
|
||
if (main_api === 'openai') {
|
||
return;
|
||
}
|
||
|
||
// Cohee: I'm not even sure what this is for anymore
|
||
if (i === arrMes.length - 1 && type !== 'continue') {
|
||
item = item.replace(/\n?$/, '');
|
||
}
|
||
|
||
mesSend[mesSend.length] = { message: item, extensionPrompts: [] };
|
||
});
|
||
}
|
||
|
||
let mesExmString = '';
|
||
|
||
function setPromptString() {
|
||
if (main_api == 'openai') {
|
||
return;
|
||
}
|
||
|
||
console.debug('--setting Prompt string');
|
||
mesExmString = pinExmString ?? mesExamplesArray.slice(0, count_exm_add).join('');
|
||
|
||
if (mesSend.length) {
|
||
mesSend[mesSend.length - 1].message = modifyLastPromptLine(mesSend[mesSend.length - 1].message);
|
||
}
|
||
}
|
||
|
||
function modifyLastPromptLine(lastMesString) {
|
||
//#########QUIET PROMPT STUFF PT2##############
|
||
|
||
// Add quiet generation prompt at depth 0
|
||
if (quiet_prompt && quiet_prompt.length) {
|
||
|
||
// here name1 is forced for all quiet prompts..why?
|
||
const name = name1;
|
||
//checks if we are in instruct, if so, formats the chat as such, otherwise just adds the quiet prompt
|
||
const quietAppend = isInstruct ? formatInstructModeChat(name, quiet_prompt, false, true, '', name1, name2, false) : `\n${quiet_prompt}`;
|
||
|
||
//This begins to fix quietPrompts (particularly /sysgen) for instruct
|
||
//previously instruct input sequence was being appended to the last chat message w/o '\n'
|
||
//and no output sequence was added after the input's content.
|
||
//TODO: respect output_sequence vs last_output_sequence settings
|
||
//TODO: decide how to prompt this to clarify who is talking 'Narrator', 'System', etc.
|
||
if (isInstruct) {
|
||
lastMesString += '\n' + quietAppend; // + power_user.instruct.output_sequence + '\n';
|
||
} else {
|
||
lastMesString += quietAppend;
|
||
}
|
||
|
||
|
||
// Ross: bailing out early prevents quiet prompts from respecting other instruct prompt toggles
|
||
// for sysgen, SD, and summary this is desireable as it prevents the AI from responding as char..
|
||
// but for idle prompting, we want the flexibility of the other prompt toggles, and to respect them as per settings in the extension
|
||
// need a detection for what the quiet prompt is being asked for...
|
||
|
||
// Bail out early?
|
||
if (quietToLoud !== true) {
|
||
return lastMesString;
|
||
}
|
||
}
|
||
|
||
|
||
// Get instruct mode line
|
||
if (isInstruct && !isContinue) {
|
||
const name = isImpersonate ? name1 : name2;
|
||
lastMesString += formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2);
|
||
}
|
||
|
||
// Get non-instruct impersonation line
|
||
if (!isInstruct && isImpersonate && !isContinue) {
|
||
const name = name1;
|
||
if (!lastMesString.endsWith('\n')) {
|
||
lastMesString += '\n';
|
||
}
|
||
lastMesString += name + ':';
|
||
}
|
||
|
||
// Add character's name
|
||
// Force name append on continue
|
||
if (!isInstruct && force_name2) {
|
||
if (!lastMesString.endsWith('\n')) {
|
||
lastMesString += '\n';
|
||
}
|
||
lastMesString += `${name2}:`;
|
||
}
|
||
|
||
return lastMesString;
|
||
}
|
||
|
||
// Clean up the already generated prompt for seamless addition
|
||
function cleanupPromptCache(promptCache) {
|
||
// Remove the first occurrance of character's name
|
||
if (promptCache.trimStart().startsWith(`${name2}:`)) {
|
||
promptCache = promptCache.replace(`${name2}:`, '').trimStart();
|
||
}
|
||
|
||
// Remove the first occurrance of prompt bias
|
||
if (promptCache.trimStart().startsWith(promptBias)) {
|
||
promptCache = promptCache.replace(promptBias, '');
|
||
}
|
||
|
||
// Add a space if prompt cache doesn't start with one
|
||
if (!/^\s/.test(promptCache) && !isInstruct) {
|
||
promptCache = ' ' + promptCache;
|
||
}
|
||
|
||
return promptCache;
|
||
}
|
||
|
||
function checkPromptSize() {
|
||
console.debug('---checking Prompt size');
|
||
setPromptString();
|
||
const prompt = [
|
||
storyString,
|
||
mesExmString,
|
||
mesSend.join(''),
|
||
generatedPromptCache,
|
||
allAnchors,
|
||
quiet_prompt,
|
||
].join('').replace(/\r/gm, '');
|
||
let thisPromptContextSize = getTokenCount(prompt, power_user.token_padding);
|
||
|
||
if (thisPromptContextSize > this_max_context) { //if the prepared prompt is larger than the max context size...
|
||
if (count_exm_add > 0) { // ..and we have example mesages..
|
||
count_exm_add--; // remove the example messages...
|
||
checkPromptSize(); // and try agin...
|
||
} else if (mesSend.length > 0) { // if the chat history is longer than 0
|
||
mesSend.shift(); // remove the first (oldest) chat entry..
|
||
checkPromptSize(); // and check size again..
|
||
} else {
|
||
//end
|
||
console.debug(`---mesSend.length = ${mesSend.length}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (generatedPromptCache.length > 0 && main_api !== 'openai') {
|
||
console.debug('---Generated Prompt Cache length: ' + generatedPromptCache.length);
|
||
checkPromptSize();
|
||
} else {
|
||
console.debug('---calling setPromptString ' + generatedPromptCache.length)
|
||
setPromptString();
|
||
}
|
||
|
||
// Fetches the combined prompt for both negative and positive prompts
|
||
const cfgGuidanceScale = getGuidanceScale();
|
||
|
||
// For prompt bit itemization
|
||
let mesSendString = '';
|
||
|
||
function getCombinedPrompt(isNegative) {
|
||
// Only return if the guidance scale doesn't exist or the value is 1
|
||
// Also don't return if constructing the neutral prompt
|
||
if (isNegative && (!cfgGuidanceScale || cfgGuidanceScale?.value === 1)) {
|
||
return;
|
||
}
|
||
|
||
// OAI has its own prompt manager. No need to do anything here
|
||
if (main_api === 'openai') {
|
||
return ''
|
||
}
|
||
|
||
// Deep clone
|
||
let finalMesSend = structuredClone(mesSend);
|
||
|
||
// TODO: Rewrite getExtensionPrompt to not require multiple for loops
|
||
// Set all extension prompts where insertion depth > mesSend length
|
||
if (finalMesSend.length) {
|
||
for (let upperDepth = MAX_INJECTION_DEPTH; upperDepth >= finalMesSend.length; upperDepth--) {
|
||
const upperAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, upperDepth);
|
||
if (upperAnchor && upperAnchor.length) {
|
||
finalMesSend[0].extensionPrompts.push(upperAnchor);
|
||
}
|
||
}
|
||
}
|
||
|
||
finalMesSend.forEach((mesItem, index) => {
|
||
if (index === 0) {
|
||
return;
|
||
}
|
||
|
||
const anchorDepth = Math.abs(index - finalMesSend.length);
|
||
// NOTE: Depth injected here!
|
||
const extensionAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, anchorDepth);
|
||
|
||
if (anchorDepth >= 0 && extensionAnchor && extensionAnchor.length) {
|
||
mesItem.extensionPrompts.push(extensionAnchor);
|
||
}
|
||
});
|
||
|
||
// TODO: Move zero-depth anchor append to work like CFG and bias appends
|
||
if (zeroDepthAnchor?.length && !isContinue) {
|
||
console.log(/\s/.test(finalMesSend[finalMesSend.length - 1].message.slice(-1)))
|
||
finalMesSend[finalMesSend.length - 1].message +=
|
||
/\s/.test(finalMesSend[finalMesSend.length - 1].message.slice(-1))
|
||
? zeroDepthAnchor
|
||
: `${zeroDepthAnchor}`;
|
||
}
|
||
|
||
let cfgPrompt = {};
|
||
if (cfgGuidanceScale && cfgGuidanceScale?.value !== 1) {
|
||
cfgPrompt = getCfgPrompt(cfgGuidanceScale, isNegative);
|
||
}
|
||
|
||
if (cfgPrompt && cfgPrompt?.value) {
|
||
if (cfgPrompt?.depth === 0) {
|
||
finalMesSend[finalMesSend.length - 1].message +=
|
||
/\s/.test(finalMesSend[finalMesSend.length - 1].message.slice(-1))
|
||
? cfgPrompt.value
|
||
: ` ${cfgPrompt.value}`;
|
||
} else {
|
||
// TODO: Make all extension prompts use an array/splice method
|
||
finalMesSend[mesSend.length - cfgPrompt.depth].extensionPrompts.push(`${cfgPrompt.value}\n`);
|
||
}
|
||
}
|
||
|
||
// Add prompt bias after everything else
|
||
// Always run with continue
|
||
if (!isInstruct && !isImpersonate) {
|
||
if (promptBias.trim().length !== 0) {
|
||
finalMesSend[finalMesSend.length - 1].message +=
|
||
/\s/.test(finalMesSend[finalMesSend.length - 1].message.slice(-1))
|
||
? promptBias.trimStart()
|
||
: ` ${promptBias.trimStart()}`;
|
||
}
|
||
}
|
||
|
||
// Prune from prompt cache if it exists
|
||
if (generatedPromptCache.length !== 0) {
|
||
generatedPromptCache = cleanupPromptCache(generatedPromptCache);
|
||
}
|
||
|
||
// Right now, everything is suffixed with a newline
|
||
mesSendString = finalMesSend.map((e) => `${e.extensionPrompts.join('')}${e.message}`).join('');
|
||
|
||
// add chat preamble
|
||
mesSendString = addChatsPreamble(mesSendString);
|
||
|
||
// add a custom dingus (if defined)
|
||
mesSendString = addChatsSeparator(mesSendString);
|
||
|
||
let combinedPrompt =
|
||
beforeScenarioAnchor +
|
||
storyString +
|
||
afterScenarioAnchor +
|
||
mesExmString +
|
||
mesSendString +
|
||
generatedPromptCache;
|
||
|
||
combinedPrompt = combinedPrompt.replace(/\r/gm, '');
|
||
|
||
if (power_user.collapse_newlines) {
|
||
combinedPrompt = collapseNewlines(combinedPrompt);
|
||
}
|
||
|
||
return combinedPrompt;
|
||
}
|
||
|
||
// Get the negative prompt first since it has the unmodified mesSend array
|
||
let negativePrompt = main_api == 'textgenerationwebui' ? getCombinedPrompt(true) : undefined;
|
||
let finalPrompt = getCombinedPrompt(false);
|
||
|
||
// Include the entire guidance scale object
|
||
const cfgValues = cfgGuidanceScale && cfgGuidanceScale?.value !== 1 ? ({ guidanceScale: cfgGuidanceScale, negativePrompt: negativePrompt }) : null;
|
||
|
||
let this_amount_gen = Number(amount_gen); // how many tokens the AI will be requested to generate
|
||
let this_settings = koboldai_settings[koboldai_setting_names[preset_settings]];
|
||
|
||
let thisPromptBits = [];
|
||
|
||
// TODO: Make this a switch
|
||
if (main_api == 'koboldhorde' && 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
|
||
}
|
||
|
||
let generate_data;
|
||
if (main_api == 'koboldhorde' || main_api == 'kobold') {
|
||
generate_data = {
|
||
prompt: finalPrompt,
|
||
gui_settings: true,
|
||
max_length: amount_gen,
|
||
max_context_length: max_context,
|
||
};
|
||
|
||
if (preset_settings != 'gui') {
|
||
const maxContext = (adjustedParams && horde_settings.auto_adjust_context_length) ? adjustedParams.maxContextLength : max_context;
|
||
generate_data = getKoboldGenerationData(finalPrompt, this_settings, this_amount_gen, maxContext, isImpersonate, type);
|
||
}
|
||
}
|
||
else if (main_api == 'textgenerationwebui') {
|
||
generate_data = getTextGenGenerationData(finalPrompt, this_amount_gen, isImpersonate, cfgValues);
|
||
generate_data.use_mancer = api_use_mancer_webui;
|
||
}
|
||
else if (main_api == 'novel') {
|
||
const this_settings = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]];
|
||
generate_data = getNovelGenerationData(finalPrompt, this_settings, this_amount_gen, isImpersonate, cfgValues);
|
||
}
|
||
else if (main_api == 'openai') {
|
||
let [prompt, counts] = prepareOpenAIMessages({
|
||
name2: name2,
|
||
charDescription: charDescription,
|
||
charPersonality: charPersonality,
|
||
Scenario: Scenario,
|
||
worldInfoBefore: worldInfoBefore,
|
||
worldInfoAfter: worldInfoAfter,
|
||
extensionPrompts: extension_prompts,
|
||
bias: promptBias,
|
||
type: type,
|
||
quietPrompt: quiet_prompt,
|
||
cyclePrompt: cyclePrompt,
|
||
systemPromptOverride: systemPrompt,
|
||
jailbreakPromptOverride: jailbreakPrompt,
|
||
personaDescription: personaDescription
|
||
}, dryRun);
|
||
generate_data = { prompt: prompt };
|
||
|
||
// counts will return false if the user has not enabled the token breakdown feature
|
||
if (counts) {
|
||
parseTokenCounts(counts, thisPromptBits);
|
||
}
|
||
|
||
if (!dryRun) {
|
||
setInContextMessages(openai_messages_count, type);
|
||
}
|
||
}
|
||
|
||
if (true === dryRun) return onSuccess({ error: 'dryRun' });
|
||
|
||
if (power_user.console_log_prompts) {
|
||
console.log(generate_data.prompt);
|
||
}
|
||
|
||
let generate_url = getGenerateUrl();
|
||
console.debug('rungenerate calling API');
|
||
|
||
showStopButton();
|
||
|
||
//set array object for prompt token itemization of this message
|
||
let currentArrayEntry = Number(thisPromptBits.length - 1);
|
||
let additionalPromptStuff = {
|
||
...thisPromptBits[currentArrayEntry],
|
||
rawPrompt: generate_data.prompt || generate_data.input,
|
||
mesId: getNextMessageId(type),
|
||
allAnchors: allAnchors,
|
||
summarizeString: (extension_prompts['1_memory']?.value || ''),
|
||
authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''),
|
||
smartContextString: (extension_prompts['chromadb']?.value || ''),
|
||
worldInfoString: worldInfoString,
|
||
storyString: storyString,
|
||
beforeScenarioAnchor: beforeScenarioAnchor,
|
||
afterScenarioAnchor: afterScenarioAnchor,
|
||
examplesString: examplesString,
|
||
mesSendString: mesSendString,
|
||
generatedPromptCache: generatedPromptCache,
|
||
promptBias: promptBias,
|
||
finalPrompt: finalPrompt,
|
||
charDescription: charDescription,
|
||
charPersonality: charPersonality,
|
||
scenarioText: scenarioText,
|
||
this_max_context: this_max_context,
|
||
padding: power_user.token_padding,
|
||
main_api: main_api,
|
||
instruction: isInstruct ? substituteParams(power_user.prefer_character_prompt && systemPrompt ? systemPrompt : power_user.instruct.system_prompt) : '',
|
||
userPersona: (power_user.persona_description || ''),
|
||
};
|
||
|
||
thisPromptBits = additionalPromptStuff;
|
||
|
||
//console.log(thisPromptBits);
|
||
|
||
itemizedPrompts.push(thisPromptBits);
|
||
console.debug(`pushed prompt bits to itemizedPrompts array. Length is now: ${itemizedPrompts.length}`);
|
||
|
||
if (main_api == 'openai') {
|
||
if (isStreamingEnabled() && type !== 'quiet') {
|
||
streamingProcessor.generator = await sendOpenAIRequest(type, generate_data.prompt, streamingProcessor.abortController.signal);
|
||
}
|
||
else {
|
||
sendOpenAIRequest(type, generate_data.prompt, abortController.signal).then(onSuccess).catch(onError);
|
||
}
|
||
}
|
||
else if (main_api == 'koboldhorde') {
|
||
generateHorde(finalPrompt, generate_data, abortController.signal).then(onSuccess).catch(onError);
|
||
}
|
||
else if (main_api == 'textgenerationwebui' && isStreamingEnabled() && type !== 'quiet') {
|
||
streamingProcessor.generator = await generateTextGenWithStreaming(generate_data, streamingProcessor.abortController.signal);
|
||
}
|
||
else if (main_api == 'novel' && isStreamingEnabled() && type !== 'quiet') {
|
||
streamingProcessor.generator = await generateNovelWithStreaming(generate_data, streamingProcessor.abortController.signal);
|
||
}
|
||
else if (main_api == 'kobold' && isStreamingEnabled() && type !== 'quiet') {
|
||
streamingProcessor.generator = await generateKoboldWithStreaming(generate_data, streamingProcessor.abortController.signal);
|
||
}
|
||
else {
|
||
try {
|
||
const response = await fetch(generate_url, {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
cache: 'no-cache',
|
||
body: JSON.stringify(generate_data),
|
||
signal: abortController.signal,
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const error = await response.json();
|
||
throw error;
|
||
}
|
||
|
||
const data = await response.json();
|
||
onSuccess(data);
|
||
} catch (error) {
|
||
onError(error);
|
||
}
|
||
}
|
||
|
||
if (isStreamingEnabled() && type !== 'quiet') {
|
||
hideSwipeButtons();
|
||
let getMessage = await streamingProcessor.generate();
|
||
let messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false);
|
||
|
||
if (isContinue) {
|
||
getMessage = continue_mag + getMessage;
|
||
}
|
||
|
||
if (streamingProcessor && !streamingProcessor.isStopped && streamingProcessor.isFinished) {
|
||
await streamingProcessor.onFinishStreaming(streamingProcessor.messageId, getMessage);
|
||
streamingProcessor = null;
|
||
triggerAutoContinue(messageChunk, isImpersonate);
|
||
}
|
||
}
|
||
|
||
async function onSuccess(data) {
|
||
let messageChunk = '';
|
||
|
||
if (data.error == 'dryRun') {
|
||
generatedPromptCache = '';
|
||
resolve();
|
||
return;
|
||
}
|
||
|
||
if (!data.error) {
|
||
//const getData = await response.json();
|
||
let getMessage = extractMessageFromData(data);
|
||
let title = extractTitleFromData(data);
|
||
kobold_horde_model = title;
|
||
|
||
messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false);
|
||
|
||
if (isContinue) {
|
||
getMessage = continue_mag + getMessage;
|
||
}
|
||
|
||
//Formating
|
||
const displayIncomplete = type == 'quiet';
|
||
getMessage = cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete);
|
||
|
||
if (getMessage.length > 0) {
|
||
if (isImpersonate) {
|
||
$('#send_textarea').val(getMessage).trigger('input');
|
||
generatedPromptCache = "";
|
||
await eventSource.emit(event_types.IMPERSONATE_READY, getMessage);
|
||
}
|
||
else if (type == 'quiet') {
|
||
resolve(getMessage);
|
||
}
|
||
else {
|
||
// Without streaming we'll be having a full message on continuation. Treat it as a last chunk.
|
||
if (originalType !== 'continue') {
|
||
({ type, getMessage } = await saveReply(type, getMessage, true, title));
|
||
}
|
||
else {
|
||
({ type, getMessage } = await saveReply('appendFinal', getMessage, true, title));
|
||
}
|
||
}
|
||
activateSendButtons();
|
||
|
||
if (type !== 'quiet') {
|
||
playMessageSound();
|
||
}
|
||
|
||
generate_loop_counter = 0;
|
||
} else {
|
||
++generate_loop_counter;
|
||
|
||
if (generate_loop_counter > MAX_GENERATION_LOOPS) {
|
||
throwCircuitBreakerError();
|
||
}
|
||
|
||
// regenerate with character speech reenforced
|
||
// to make sure we leave on swipe type while also adding the name2 appendage
|
||
setTimeout(() => {
|
||
Generate(type, { automatic_trigger, force_name2: true, resolve, reject, quiet_prompt, force_chid });
|
||
}, generate_loop_counter * 1000);
|
||
}
|
||
|
||
if (power_user.auto_swipe) {
|
||
console.debug('checking for autoswipeblacklist on non-streaming message');
|
||
function containsBlacklistedWords(getMessage, blacklist, threshold) {
|
||
console.debug('checking blacklisted words');
|
||
const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi');
|
||
const matches = getMessage.match(regex) || [];
|
||
return matches.length >= threshold;
|
||
}
|
||
|
||
const generatedTextFiltered = (getMessage) => {
|
||
if (power_user.auto_swipe_blacklist_threshold) {
|
||
if (containsBlacklistedWords(getMessage, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) {
|
||
console.debug("Generated text has blacklisted words")
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
if (generatedTextFiltered(getMessage)) {
|
||
console.debug('swiping right automatically');
|
||
is_send_press = false;
|
||
swipe_right();
|
||
return
|
||
}
|
||
}
|
||
} else {
|
||
generatedPromptCache = '';
|
||
activateSendButtons();
|
||
//console.log('runGenerate calling showSwipeBtns');
|
||
showSwipeButtons();
|
||
|
||
if (main_api == 'textgenerationwebui' && api_use_mancer_webui) {
|
||
const errorText = `<h3>Inferencer endpoint is unhappy!</h3>
|
||
Returned status <tt>${data.status}</tt> with the reason:<br/>
|
||
${data.response}`;
|
||
callPopup(errorText, 'text');
|
||
}
|
||
}
|
||
console.debug('/savechat called by /Generate');
|
||
|
||
await saveChatConditional();
|
||
is_send_press = false;
|
||
hideStopButton();
|
||
activateSendButtons();
|
||
showSwipeButtons();
|
||
setGenerationProgress(0);
|
||
|
||
if (type !== 'quiet') {
|
||
triggerAutoContinue(messageChunk, isImpersonate);
|
||
resolve();
|
||
}
|
||
};
|
||
|
||
function onError(exception) {
|
||
if (typeof exception?.error?.message === 'string') {
|
||
toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 });
|
||
}
|
||
|
||
reject(exception);
|
||
$("#send_textarea").removeAttr('disabled');
|
||
is_send_press = false;
|
||
activateSendButtons();
|
||
showSwipeButtons();
|
||
setGenerationProgress(0);
|
||
console.log(exception);
|
||
};
|
||
|
||
} //rungenerate ends
|
||
} else { //generate's primary loop ends, after this is error handling for no-connection or safety-id
|
||
if (this_chid === undefined || this_chid === 'invalid-safety-id') {
|
||
toastr.warning('Сharacter is not selected');
|
||
}
|
||
is_send_press = false;
|
||
}
|
||
|
||
//prevent custom depth WI entries (which have unique random key names) from duplicating
|
||
for (let key in extension_prompts) {
|
||
if (key.includes('customDepthWI')) {
|
||
let keyname = extension_prompts[key]
|
||
delete extension_prompts[key];
|
||
}
|
||
}
|
||
//console.log('generate ending');
|
||
} //generate ends
|
||
|
||
function getNextMessageId(type) {
|
||
return type == 'swipe' ? Number(count_view_mes - 1) : Number(count_view_mes);
|
||
}
|
||
|
||
/**
|
||
*
|
||
* @param {string} messageChunk
|
||
* @param {boolean} isImpersonate
|
||
* @returns {void}
|
||
*/
|
||
export function triggerAutoContinue(messageChunk, isImpersonate) {
|
||
if (selected_group) {
|
||
console.log('Auto-continue is disabled for group chat');
|
||
return;
|
||
}
|
||
|
||
if (power_user.auto_continue.enabled && !is_send_press) {
|
||
if (power_user.auto_continue.target_length <= 0) {
|
||
console.log('Auto-continue target length is 0, not triggering auto-continue');
|
||
return;
|
||
}
|
||
|
||
if (main_api === 'openai' && !power_user.auto_continue.allow_chat_completions) {
|
||
console.log('Auto-continue for OpenAI is disabled by user.');
|
||
return;
|
||
}
|
||
|
||
if (isImpersonate) {
|
||
console.log('Continue for impersonation is not implemented yet');
|
||
return;
|
||
}
|
||
|
||
const textareaText = String($('#send_textarea').val());
|
||
const USABLE_LENGTH = 5;
|
||
|
||
if (textareaText.length > 0) {
|
||
console.log('Not triggering auto-continue because user input is not empty');
|
||
return;
|
||
}
|
||
|
||
if (messageChunk.trim().length > USABLE_LENGTH && chat.length) {
|
||
const lastMessage = chat[chat.length - 1];
|
||
const messageLength = getTokenCount(lastMessage.mes);
|
||
const shouldAutoContinue = messageLength < power_user.auto_continue.target_length;
|
||
|
||
if (shouldAutoContinue) {
|
||
console.log(`Triggering auto-continue. Message tokens: ${messageLength}. Target tokens: ${power_user.auto_continue.target_length}. Message chunk: ${messageChunk}`);
|
||
$("#option_continue").trigger('click');
|
||
} else {
|
||
console.log(`Not triggering auto-continue. Message tokens: ${messageLength}. Target tokens: ${power_user.auto_continue.target_length}`);
|
||
return;
|
||
}
|
||
} else {
|
||
console.log('Last generated chunk was empty, not triggering auto-continue');
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
export function getBiasStrings(textareaText, type) {
|
||
if (type == 'impersonate' || type == 'continue') {
|
||
return { messageBias: '', promptBias: '', isUserPromptBias: false };
|
||
}
|
||
|
||
let promptBias = '';
|
||
let messageBias = extractMessageBias(textareaText);
|
||
|
||
// If user input is not provided, retrieve the bias of the most recent relevant message
|
||
if (!textareaText) {
|
||
for (let i = chat.length - 1; i >= 0; i--) {
|
||
const mes = chat[i];
|
||
if (type === 'swipe' && chat.length - 1 === i) {
|
||
continue;
|
||
}
|
||
if (mes && (mes.is_user || mes.is_system || mes.extra?.type === system_message_types.NARRATOR)) {
|
||
if (mes.extra?.bias?.trim()?.length > 0) {
|
||
promptBias = mes.extra.bias;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
promptBias = messageBias || promptBias || power_user.user_prompt_bias || '';
|
||
const isUserPromptBias = promptBias === power_user.user_prompt_bias;
|
||
|
||
// Substitute params for everything
|
||
messageBias = substituteParams(messageBias);
|
||
promptBias = substituteParams(promptBias);
|
||
|
||
return { messageBias, promptBias, isUserPromptBias };
|
||
}
|
||
|
||
/**
|
||
* @param {Object} chatItem Message history item.
|
||
* @param {boolean} isInstruct Whether instruct mode is enabled.
|
||
* @param {boolean|number} forceOutputSequence Whether to force the first/last output sequence for instruct mode.
|
||
*/
|
||
function formatMessageHistoryItem(chatItem, isInstruct, forceOutputSequence) {
|
||
const isNarratorType = chatItem?.extra?.type === system_message_types.NARRATOR;
|
||
const characterName = (selected_group || chatItem.force_avatar) ? chatItem.name : name2;
|
||
const itemName = chatItem.is_user ? chatItem['name'] : characterName;
|
||
const shouldPrependName = !isNarratorType;
|
||
|
||
let textResult = shouldPrependName ? `${itemName}: ${chatItem.mes}\n` : `${chatItem.mes}\n`;
|
||
|
||
if (isInstruct) {
|
||
textResult = formatInstructModeChat(itemName, chatItem.mes, chatItem.is_user, isNarratorType, chatItem.force_avatar, name1, name2, forceOutputSequence);
|
||
}
|
||
|
||
textResult = replaceBiasMarkup(textResult);
|
||
|
||
return textResult;
|
||
}
|
||
|
||
export function replaceBiasMarkup(str) {
|
||
return (str ?? '').replace(/\{\{[\s\S]*?\}\}/gm, '');
|
||
}
|
||
|
||
export async function sendMessageAsUser(textareaText, messageBias) {
|
||
textareaText = getRegexedString(textareaText, regex_placement.USER_INPUT);
|
||
|
||
chat[chat.length] = {};
|
||
chat[chat.length - 1]['name'] = name1;
|
||
chat[chat.length - 1]['is_user'] = true;
|
||
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
|
||
chat[chat.length - 1]['mes'] = substituteParams(textareaText);
|
||
chat[chat.length - 1]['extra'] = {};
|
||
|
||
if (power_user.message_token_count_enabled) {
|
||
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
|
||
}
|
||
|
||
// Lock user avatar to a persona.
|
||
if (user_avatar in power_user.personas) {
|
||
chat[chat.length - 1]['force_avatar'] = getUserAvatar(user_avatar);
|
||
}
|
||
|
||
if (messageBias) {
|
||
console.debug('checking bias');
|
||
chat[chat.length - 1]['extra']['bias'] = messageBias;
|
||
}
|
||
statMesProcess(chat[chat.length - 1], 'user', characters, this_chid, '');
|
||
// Wait for all handlers to finish before continuing with the prompt
|
||
await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
|
||
addOneMessage(chat[chat.length - 1]);
|
||
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1));
|
||
console.debug('message sent as user');
|
||
}
|
||
|
||
function getMaxContextSize() {
|
||
let this_max_context = 1487;
|
||
if (main_api == 'kobold' || main_api == 'koboldhorde' || main_api == 'textgenerationwebui') {
|
||
this_max_context = (max_context - amount_gen);
|
||
}
|
||
if (main_api == 'novel') {
|
||
this_max_context = Number(max_context);
|
||
if (nai_settings.model_novel.includes('clio')) {
|
||
this_max_context = Math.min(max_context, 8192);
|
||
}
|
||
if (nai_settings.model_novel.includes('kayra')) {
|
||
this_max_context = Math.min(max_context, 8192);
|
||
|
||
const subscriptionLimit = getKayraMaxContextTokens();
|
||
if (typeof subscriptionLimit === "number" && this_max_context > subscriptionLimit) {
|
||
this_max_context = subscriptionLimit;
|
||
console.log(`NovelAI subscription limit reached. Max context size is now ${this_max_context}`);
|
||
}
|
||
}
|
||
|
||
this_max_context = this_max_context - amount_gen;
|
||
}
|
||
if (main_api == 'openai') {
|
||
this_max_context = oai_settings.openai_max_context;
|
||
}
|
||
return this_max_context;
|
||
}
|
||
|
||
function parseTokenCounts(counts, thisPromptBits) {
|
||
/**
|
||
* @param {any[]} numbers
|
||
*/
|
||
function getSum(...numbers) {
|
||
return numbers.map(x => Number(x)).filter(x => !Number.isNaN(x)).reduce((acc, val) => acc + val, 0);
|
||
}
|
||
const total = getSum(Object.values(counts));
|
||
|
||
thisPromptBits.push({
|
||
oaiStartTokens: (counts?.start + counts?.controlPrompts) || 0,
|
||
oaiPromptTokens: getSum(counts?.prompt, counts?.charDescription, counts?.charPersonality, counts?.scenario) || 0,
|
||
oaiBiasTokens: counts?.bias || 0,
|
||
oaiNudgeTokens: counts?.nudge || 0,
|
||
oaiJailbreakTokens: counts?.jailbreak || 0,
|
||
oaiImpersonateTokens: counts?.impersonate || 0,
|
||
oaiExamplesTokens: (counts?.dialogueExamples + counts?.examples) || 0,
|
||
oaiConversationTokens: (counts?.conversation + counts?.chatHistory) || 0,
|
||
oaiNsfwTokens: counts?.nsfw || 0,
|
||
oaiMainTokens: counts?.main || 0,
|
||
oaiTotalTokens: total,
|
||
});
|
||
}
|
||
|
||
function addChatsPreamble(mesSendString) {
|
||
return main_api === 'novel'
|
||
? substituteParams(nai_settings.preamble) + '\n' + mesSendString
|
||
: mesSendString;
|
||
}
|
||
|
||
function addChatsSeparator(mesSendString) {
|
||
if (power_user.context.chat_start) {
|
||
return substituteParams(power_user.context.chat_start) + '\n' + mesSendString;
|
||
}
|
||
|
||
else {
|
||
return mesSendString;
|
||
}
|
||
}
|
||
|
||
function appendZeroDepthAnchor(force_name2, zeroDepthAnchor, finalPrompt) {
|
||
const trimBothEnds = !force_name2;
|
||
let trimmedPrompt = (trimBothEnds ? zeroDepthAnchor.trim() : zeroDepthAnchor.trimEnd());
|
||
|
||
if (trimBothEnds && !finalPrompt.endsWith('\n')) {
|
||
finalPrompt += '\n';
|
||
}
|
||
|
||
finalPrompt += trimmedPrompt;
|
||
|
||
if (force_name2) {
|
||
finalPrompt += ' ';
|
||
}
|
||
|
||
return finalPrompt;
|
||
}
|
||
|
||
async function DupeChar() {
|
||
if (!this_chid) {
|
||
toastr.warning('You must first select a character to duplicate!')
|
||
return;
|
||
}
|
||
|
||
const confirm = await callPopup(`
|
||
<h3>Are you sure you want to duplicate this character?</h3>
|
||
<span>If you just want to start a new chat with the same character, use "Start new chat" option in the bottom-left options menu.</span><br><br>`,
|
||
'confirm',
|
||
);
|
||
|
||
if (!confirm) {
|
||
console.log('User cancelled duplication');
|
||
return;
|
||
}
|
||
|
||
const body = { avatar_url: characters[this_chid].avatar };
|
||
const response = await fetch('/dupecharacter', {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify(body),
|
||
});
|
||
if (response.ok) {
|
||
toastr.success("Character Duplicated");
|
||
getCharacters();
|
||
}
|
||
}
|
||
|
||
function promptItemize(itemizedPrompts, requestedMesId) {
|
||
console.log('PROMPT ITEMIZE ENTERED');
|
||
var incomingMesId = Number(requestedMesId);
|
||
console.debug(`looking for MesId ${incomingMesId}`);
|
||
var thisPromptSet = undefined;
|
||
|
||
for (var i = 0; i < itemizedPrompts.length; i++) {
|
||
console.log(`looking for ${incomingMesId} vs ${itemizedPrompts[i].mesId}`)
|
||
if (itemizedPrompts[i].mesId === incomingMesId) {
|
||
console.log(`found matching mesID ${i}`);
|
||
thisPromptSet = i;
|
||
PromptArrayItemForRawPromptDisplay = i;
|
||
console.log(`wanting to raw display of ArrayItem: ${PromptArrayItemForRawPromptDisplay} which is mesID ${incomingMesId}`);
|
||
console.log(itemizedPrompts[thisPromptSet]);
|
||
}
|
||
}
|
||
|
||
if (thisPromptSet === undefined) {
|
||
console.log(`couldnt find the right mesId. looked for ${incomingMesId}`);
|
||
console.log(itemizedPrompts);
|
||
return null;
|
||
}
|
||
|
||
//these happen regardless of API
|
||
var charDescriptionTokens = getTokenCount(itemizedPrompts[thisPromptSet].charDescription);
|
||
var charPersonalityTokens = getTokenCount(itemizedPrompts[thisPromptSet].charPersonality);
|
||
var scenarioTextTokens = getTokenCount(itemizedPrompts[thisPromptSet].scenarioText);
|
||
var userPersonaStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].userPersona);
|
||
var worldInfoStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].worldInfoString);
|
||
var allAnchorsTokens = getTokenCount(itemizedPrompts[thisPromptSet].allAnchors);
|
||
var summarizeStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].summarizeString);
|
||
var authorsNoteStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].authorsNoteString);
|
||
var smartContextStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].smartContextString);
|
||
var beforeScenarioAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].beforeScenarioAnchor);
|
||
var afterScenarioAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].afterScenarioAnchor);
|
||
var zeroDepthAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].zeroDepthAnchor);
|
||
var thisPrompt_max_context = itemizedPrompts[thisPromptSet].this_max_context;
|
||
var thisPrompt_padding = itemizedPrompts[thisPromptSet].padding;
|
||
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 oaiMainTokens = itemizedPrompts[thisPromptSet].oaiMainTokens;
|
||
var oaiStartTokens = itemizedPrompts[thisPromptSet].oaiStartTokens;
|
||
var ActualChatHistoryTokens = itemizedPrompts[thisPromptSet].oaiConversationTokens;
|
||
var examplesStringTokens = itemizedPrompts[thisPromptSet].oaiExamplesTokens;
|
||
var oaiPromptTokens = itemizedPrompts[thisPromptSet].oaiPromptTokens - (afterScenarioAnchorTokens + beforeScenarioAnchorTokens) + examplesStringTokens;
|
||
var oaiBiasTokens = itemizedPrompts[thisPromptSet].oaiBiasTokens;
|
||
var oaiJailbreakTokens = itemizedPrompts[thisPromptSet].oaiJailbreakTokens;
|
||
var oaiNudgeTokens = itemizedPrompts[thisPromptSet].oaiNudgeTokens;
|
||
var oaiImpersonateTokens = itemizedPrompts[thisPromptSet].oaiImpersonateTokens;
|
||
var oaiNsfwTokens = itemizedPrompts[thisPromptSet].oaiNsfwTokens;
|
||
var finalPromptTokens =
|
||
oaiStartTokens +
|
||
oaiPromptTokens +
|
||
oaiMainTokens +
|
||
oaiNsfwTokens +
|
||
oaiBiasTokens +
|
||
oaiImpersonateTokens +
|
||
oaiJailbreakTokens +
|
||
oaiNudgeTokens +
|
||
ActualChatHistoryTokens +
|
||
//charDescriptionTokens +
|
||
//charPersonalityTokens +
|
||
//allAnchorsTokens +
|
||
worldInfoStringTokens +
|
||
beforeScenarioAnchorTokens +
|
||
afterScenarioAnchorTokens;
|
||
// 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].finalPrompt);
|
||
var storyStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].storyString) - worldInfoStringTokens;
|
||
var examplesStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].examplesString);
|
||
var mesSendStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].mesSendString)
|
||
var ActualChatHistoryTokens = mesSendStringTokens - (allAnchorsTokens - (beforeScenarioAnchorTokens + afterScenarioAnchorTokens)) + power_user.token_padding;
|
||
var instructionTokens = getTokenCount(itemizedPrompts[thisPromptSet].instruction);
|
||
var promptBiasTokens = getTokenCount(itemizedPrompts[thisPromptSet].promptBias);
|
||
|
||
var totalTokensInPrompt =
|
||
storyStringTokens + //chardefs total
|
||
worldInfoStringTokens +
|
||
examplesStringTokens + // example messages
|
||
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 'finalPrompt'
|
||
}
|
||
|
||
if (this_main_api == 'openai') {
|
||
//console.log('-- applying % on OAI tokens');
|
||
var oaiStartTokensPercentage = ((oaiStartTokens / (finalPromptTokens)) * 100).toFixed(2);
|
||
var storyStringTokensPercentage = (((afterScenarioAnchorTokens + beforeScenarioAnchorTokens + 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 (${getTokenizerModel()})`;
|
||
var oaiSystemTokens = oaiImpersonateTokens + oaiJailbreakTokens + oaiNudgeTokens + oaiStartTokens + oaiNsfwTokens + oaiMainTokens;
|
||
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();
|
||
}
|
||
|
||
const params = {
|
||
selectedTokenizer,
|
||
this_main_api,
|
||
storyStringTokensPercentage,
|
||
worldInfoStringTokensPercentage,
|
||
ActualChatHistoryTokensPercentage,
|
||
allAnchorsTokensPercentage,
|
||
promptBiasTokensPercentage,
|
||
storyStringTokens,
|
||
charDescriptionTokens,
|
||
charPersonalityTokens,
|
||
scenarioTextTokens,
|
||
examplesStringTokens,
|
||
userPersonaStringTokens,
|
||
instructionTokens,
|
||
worldInfoStringTokens,
|
||
ActualChatHistoryTokens,
|
||
allAnchorsTokens,
|
||
summarizeStringTokens,
|
||
authorsNoteStringTokens,
|
||
smartContextStringTokens,
|
||
promptBiasTokens,
|
||
totalTokensInPrompt,
|
||
finalPromptTokens,
|
||
thisPrompt_max_context,
|
||
thisPrompt_padding,
|
||
thisPrompt_actual: thisPrompt_max_context - thisPrompt_padding,
|
||
oaiSystemTokensPercentage,
|
||
oaiStartTokensPercentage,
|
||
oaiSystemTokens,
|
||
oaiStartTokens,
|
||
oaiJailbreakTokens,
|
||
oaiNudgeTokens,
|
||
oaiImpersonateTokens,
|
||
oaiPromptTokens,
|
||
oaiBiasTokens,
|
||
oaiNsfwTokens,
|
||
oaiMainTokens,
|
||
};
|
||
|
||
if (this_main_api == 'openai') {
|
||
callPopup(renderTemplate('itemizationChat', params), 'text');
|
||
|
||
} else {
|
||
callPopup(renderTemplate('itemizationText', params), 'text');
|
||
}
|
||
}
|
||
|
||
function setInContextMessages(lastmsg, type) {
|
||
$("#chat .mes").removeClass('lastInContext');
|
||
|
||
if (type === 'swipe' || type === 'regenerate' || type === 'continue') {
|
||
lastmsg++;
|
||
}
|
||
|
||
const lastMessageBlock = $('#chat .mes:not([is_system="true"])').eq(-lastmsg);
|
||
lastMessageBlock.addClass('lastInContext');
|
||
|
||
if (lastMessageBlock.length === 0) {
|
||
const firstMessageId = getFirstDisplayedMessageId();
|
||
$(`#chat .mes[mesid="${firstMessageId}"`).addClass('lastInContext');
|
||
}
|
||
}
|
||
|
||
function getGenerateUrl() {
|
||
let generate_url = '';
|
||
if (main_api == 'kobold') {
|
||
generate_url = '/generate';
|
||
} else if (main_api == 'textgenerationwebui') {
|
||
generate_url = '/generate_textgenerationwebui';
|
||
} else if (main_api == 'novel') {
|
||
generate_url = '/api/novelai/generate';
|
||
}
|
||
return generate_url;
|
||
}
|
||
|
||
function throwCircuitBreakerError() {
|
||
callPopup(`Could not extract reply in ${MAX_GENERATION_LOOPS} attempts. Try generating again`, 'text');
|
||
generate_loop_counter = 0;
|
||
$("#send_textarea").removeAttr('disabled');
|
||
is_send_press = false;
|
||
activateSendButtons();
|
||
setGenerationProgress(0);
|
||
showSwipeButtons();
|
||
throw new Error('Generate circuit breaker interruption');
|
||
}
|
||
|
||
function extractTitleFromData(data) {
|
||
if (main_api == 'koboldhorde') {
|
||
return data.workerName;
|
||
}
|
||
|
||
return undefined;
|
||
}
|
||
|
||
function extractMessageFromData(data) {
|
||
switch (main_api) {
|
||
case 'kobold':
|
||
return data.results[0].text;
|
||
case 'koboldhorde':
|
||
return data.text;
|
||
case 'textgenerationwebui':
|
||
return data.results[0].text;
|
||
case 'novel':
|
||
return data.output;
|
||
case 'openai':
|
||
return data;
|
||
default:
|
||
return ''
|
||
}
|
||
}
|
||
|
||
function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncompleteSentences = false) {
|
||
// Add the prompt bias before anything else
|
||
if (
|
||
power_user.user_prompt_bias &&
|
||
!isImpersonate &&
|
||
!isContinue &&
|
||
power_user.user_prompt_bias.length !== 0
|
||
) {
|
||
getMessage = substituteParams(power_user.user_prompt_bias) + getMessage;
|
||
}
|
||
|
||
// Regex uses vars, so add before formatting
|
||
getMessage = getRegexedString(getMessage, isImpersonate ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT);
|
||
|
||
if (!displayIncompleteSentences && power_user.trim_sentences) {
|
||
getMessage = trimToEndSentence(getMessage, power_user.include_newline);
|
||
}
|
||
|
||
if (power_user.collapse_newlines) {
|
||
getMessage = collapseNewlines(getMessage);
|
||
}
|
||
|
||
if (power_user.trim_spaces) {
|
||
getMessage = getMessage.trim();
|
||
}
|
||
// trailing invisible whitespace before every newlines, on a multiline string
|
||
// "trailing whitespace on newlines \nevery line of the string \n?sample text" ->
|
||
// "trailing whitespace on newlines\nevery line of the string\nsample text"
|
||
getMessage = getMessage.replace(/[^\S\r\n]+$/gm, "");
|
||
|
||
let nameToTrim = isImpersonate ? name2 : name1;
|
||
|
||
if (isImpersonate) {
|
||
nameToTrim = power_user.allow_name2_display ? '' : name2;
|
||
}
|
||
else {
|
||
nameToTrim = power_user.allow_name1_display ? '' : name1;
|
||
}
|
||
|
||
if (nameToTrim && getMessage.indexOf(`${nameToTrim}:`) == 0) {
|
||
getMessage = getMessage.substr(0, getMessage.indexOf(`${nameToTrim}:`));
|
||
}
|
||
if (nameToTrim && getMessage.indexOf(`\n${nameToTrim}:`) >= 0) {
|
||
getMessage = getMessage.substr(0, getMessage.indexOf(`\n${nameToTrim}:`));
|
||
}
|
||
if (getMessage.indexOf('<|endoftext|>') != -1) {
|
||
getMessage = getMessage.substr(0, getMessage.indexOf('<|endoftext|>'));
|
||
}
|
||
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
|
||
if (isInstruct && power_user.instruct.stop_sequence) {
|
||
if (getMessage.indexOf(power_user.instruct.stop_sequence) != -1) {
|
||
getMessage = getMessage.substring(0, getMessage.indexOf(power_user.instruct.stop_sequence));
|
||
}
|
||
}
|
||
// Hana: Only use the first sequence (should be <|model|>)
|
||
// of the prompt before <|user|> (as KoboldAI Lite does it).
|
||
if (isInstruct && power_user.instruct.input_sequence) {
|
||
if (getMessage.indexOf(power_user.instruct.input_sequence) != -1) {
|
||
getMessage = getMessage.substring(0, getMessage.indexOf(power_user.instruct.input_sequence));
|
||
}
|
||
}
|
||
if (isInstruct && power_user.instruct.input_sequence && isImpersonate) {
|
||
//getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, '');
|
||
power_user.instruct.input_sequence.split('\n')
|
||
.filter(line => line.trim() !== '')
|
||
.forEach(line => {
|
||
getMessage = getMessage.replaceAll(line, '');
|
||
});
|
||
}
|
||
if (isInstruct && power_user.instruct.output_sequence && !isImpersonate) {
|
||
//getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, '');
|
||
power_user.instruct.output_sequence.split('\n')
|
||
.filter(line => line.trim() !== '')
|
||
.forEach(line => {
|
||
getMessage = getMessage.replaceAll(line, '');
|
||
});
|
||
}
|
||
if (isInstruct && power_user.instruct.last_output_sequence && !isImpersonate) {
|
||
//getMessage = getMessage.replaceAll(power_user.instruct.last_output_sequence, '');
|
||
power_user.instruct.last_output_sequence.split('\n')
|
||
.filter(line => line.trim() !== '')
|
||
.forEach(line => {
|
||
getMessage = getMessage.replaceAll(line, '');
|
||
});
|
||
}
|
||
// clean-up group message from excessive generations
|
||
if (selected_group) {
|
||
getMessage = cleanGroupMessage(getMessage);
|
||
}
|
||
|
||
if (!power_user.allow_name2_display) {
|
||
getMessage = getMessage.replace(new RegExp(`(^|\n)${name2}:`, 'g'), "$1");
|
||
}
|
||
|
||
if (isImpersonate) {
|
||
getMessage = getMessage.trim();
|
||
}
|
||
|
||
const stoppingStrings = getStoppingStrings(isImpersonate);
|
||
|
||
for (const stoppingString of stoppingStrings) {
|
||
if (stoppingString.length) {
|
||
for (let j = stoppingString.length - 1; j > 0; j--) {
|
||
if (getMessage.slice(-j) === stoppingString.slice(0, j)) {
|
||
getMessage = getMessage.slice(0, -j);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (power_user.auto_fix_generated_markdown) {
|
||
getMessage = fixMarkdown(getMessage, false);
|
||
}
|
||
|
||
const nameToTrim2 = isImpersonate ? name1 : name2;
|
||
|
||
if (getMessage.startsWith(nameToTrim2 + ":")) {
|
||
getMessage = getMessage.replace(nameToTrim2 + ':', '');
|
||
getMessage = getMessage.trimStart();
|
||
}
|
||
|
||
if (isImpersonate) {
|
||
getMessage = getMessage.trim();
|
||
}
|
||
|
||
return getMessage;
|
||
}
|
||
|
||
async function saveReply(type, getMessage, _, title) {
|
||
if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||
|
||
chat[chat.length - 1]['is_user'])) {
|
||
type = 'normal';
|
||
}
|
||
|
||
if (chat.length && typeof chat[chat.length - 1]['extra'] !== 'object') {
|
||
chat[chat.length - 1]['extra'] = {};
|
||
}
|
||
|
||
let oldMessage = ''
|
||
const generationFinished = new Date();
|
||
const img = extractImageFromMessage(getMessage);
|
||
getMessage = img.getMessage;
|
||
if (type === 'swipe') {
|
||
oldMessage = chat[chat.length - 1]['mes'];
|
||
chat[chat.length - 1]['swipes'].length++;
|
||
if (chat[chat.length - 1]['swipe_id'] === chat[chat.length - 1]['swipes'].length - 1) {
|
||
chat[chat.length - 1]['title'] = title;
|
||
chat[chat.length - 1]['mes'] = getMessage;
|
||
chat[chat.length - 1]['gen_started'] = generation_started;
|
||
chat[chat.length - 1]['gen_finished'] = generationFinished;
|
||
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
|
||
chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
|
||
chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
|
||
if (power_user.message_token_count_enabled) {
|
||
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
|
||
}
|
||
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
|
||
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
|
||
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
|
||
} else {
|
||
chat[chat.length - 1]['mes'] = getMessage;
|
||
}
|
||
} else if (type === 'append' || type === 'continue') {
|
||
console.debug("Trying to append.")
|
||
oldMessage = chat[chat.length - 1]['mes'];
|
||
chat[chat.length - 1]['title'] = title;
|
||
chat[chat.length - 1]['mes'] += getMessage;
|
||
chat[chat.length - 1]['gen_started'] = generation_started;
|
||
chat[chat.length - 1]['gen_finished'] = generationFinished;
|
||
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
|
||
chat[chat.length - 1]["extra"]["api"] = getGeneratingApi();
|
||
chat[chat.length - 1]["extra"]["model"] = getGeneratingModel();
|
||
if (power_user.message_token_count_enabled) {
|
||
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
|
||
}
|
||
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
|
||
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
|
||
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
|
||
} else if (type === 'appendFinal') {
|
||
oldMessage = chat[chat.length - 1]['mes'];
|
||
console.debug("Trying to appendFinal.")
|
||
chat[chat.length - 1]['title'] = title;
|
||
chat[chat.length - 1]['mes'] = getMessage;
|
||
chat[chat.length - 1]['gen_started'] = generation_started;
|
||
chat[chat.length - 1]['gen_finished'] = generationFinished;
|
||
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
|
||
chat[chat.length - 1]["extra"]["api"] = getGeneratingApi();
|
||
chat[chat.length - 1]["extra"]["model"] = getGeneratingModel();
|
||
if (power_user.message_token_count_enabled) {
|
||
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
|
||
}
|
||
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
|
||
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
|
||
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
|
||
|
||
} else {
|
||
console.debug('entering chat update routine for non-swipe post');
|
||
chat[chat.length] = {};
|
||
chat[chat.length - 1]['extra'] = {};
|
||
chat[chat.length - 1]['name'] = name2;
|
||
chat[chat.length - 1]['is_user'] = false;
|
||
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
|
||
chat[chat.length - 1]["extra"]["api"] = getGeneratingApi();
|
||
chat[chat.length - 1]["extra"]["model"] = getGeneratingModel();
|
||
if (power_user.trim_spaces) {
|
||
getMessage = getMessage.trim();
|
||
}
|
||
chat[chat.length - 1]['mes'] = getMessage;
|
||
chat[chat.length - 1]['title'] = title;
|
||
chat[chat.length - 1]['gen_started'] = generation_started;
|
||
chat[chat.length - 1]['gen_finished'] = generationFinished;
|
||
|
||
if (power_user.message_token_count_enabled) {
|
||
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
|
||
}
|
||
|
||
if (selected_group) {
|
||
console.debug('entering chat update for groups');
|
||
let avatarImg = 'img/ai4.png';
|
||
if (characters[this_chid].avatar != 'none') {
|
||
avatarImg = getThumbnailUrl('avatar', characters[this_chid].avatar);
|
||
}
|
||
chat[chat.length - 1]['force_avatar'] = avatarImg;
|
||
chat[chat.length - 1]['original_avatar'] = characters[this_chid].avatar;
|
||
chat[chat.length - 1]['extra']['gen_id'] = group_generation_id;
|
||
}
|
||
|
||
saveImageToMessage(img, chat[chat.length - 1]);
|
||
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
|
||
addOneMessage(chat[chat.length - 1]);
|
||
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
|
||
}
|
||
|
||
const item = chat[chat.length - 1];
|
||
if (item["swipe_info"] === undefined) {
|
||
item["swipe_info"] = [];
|
||
}
|
||
if (item["swipe_id"] !== undefined) {
|
||
const swipeId = item["swipe_id"];
|
||
item["swipes"][swipeId] = item["mes"];
|
||
item["swipe_info"][swipeId] = {
|
||
send_date: item["send_date"],
|
||
gen_started: item["gen_started"],
|
||
gen_finished: item["gen_finished"],
|
||
extra: JSON.parse(JSON.stringify(item["extra"])),
|
||
};
|
||
} else {
|
||
item["swipe_id"] = 0;
|
||
item["swipes"] = [];
|
||
item["swipes"][0] = chat[chat.length - 1]["mes"];
|
||
item["swipe_info"][0] = {
|
||
send_date: chat[chat.length - 1]["send_date"],
|
||
gen_started: chat[chat.length - 1]["gen_started"],
|
||
gen_finished: chat[chat.length - 1]["gen_finished"],
|
||
extra: JSON.parse(JSON.stringify(chat[chat.length - 1]["extra"])),
|
||
};
|
||
}
|
||
statMesProcess(chat[chat.length - 1], type, characters, this_chid, oldMessage);
|
||
return { type, getMessage };
|
||
}
|
||
|
||
function saveImageToMessage(img, mes) {
|
||
if (mes && img.image) {
|
||
if (typeof mes.extra !== 'object') {
|
||
mes.extra = {};
|
||
}
|
||
mes.extra.image = img.image;
|
||
mes.extra.title = img.title;
|
||
}
|
||
}
|
||
|
||
function getGeneratingApi() {
|
||
switch (main_api) {
|
||
case 'openai':
|
||
return oai_settings.chat_completion_source || 'openai';
|
||
default:
|
||
return main_api;
|
||
}
|
||
}
|
||
|
||
function getGeneratingModel(mes) {
|
||
let model = '';
|
||
switch (main_api) {
|
||
case 'kobold':
|
||
model = online_status;
|
||
break;
|
||
case 'novel':
|
||
model = nai_settings.model_novel;
|
||
break;
|
||
case 'openai':
|
||
model = getChatCompletionModel();
|
||
break;
|
||
case 'textgenerationwebui':
|
||
model = online_status;
|
||
break;
|
||
case 'koboldhorde':
|
||
model = kobold_horde_model;
|
||
break;
|
||
}
|
||
return model
|
||
}
|
||
|
||
function extractImageFromMessage(getMessage) {
|
||
const regex = /<img src="(.*?)".*?alt="(.*?)".*?>/g;
|
||
const results = regex.exec(getMessage);
|
||
const image = results ? results[1] : '';
|
||
const title = results ? results[2] : '';
|
||
getMessage = getMessage.replace(regex, '');
|
||
return { getMessage, image, title };
|
||
}
|
||
|
||
export function activateSendButtons() {
|
||
is_send_press = false;
|
||
$("#send_but").removeClass("displayNone");
|
||
$("#mes_continue").removeClass("displayNone");
|
||
$("#send_textarea").attr("disabled", false);
|
||
$('.mes_buttons:last').show();
|
||
hideStopButton();
|
||
}
|
||
|
||
export function deactivateSendButtons() {
|
||
$("#send_but").addClass("displayNone");
|
||
$("#mes_continue").addClass("displayNone");
|
||
showStopButton();
|
||
}
|
||
|
||
function resetChatState() {
|
||
//unsets expected chid before reloading (related to getCharacters/printCharacters from using old arrays)
|
||
this_chid = "invalid-safety-id";
|
||
// replaces deleted charcter name with system user since it will be displayed next.
|
||
name2 = systemUserName;
|
||
// sets up system user to tell user about having deleted a character
|
||
chat = [...safetychat];
|
||
// resets chat metadata
|
||
chat_metadata = {};
|
||
// resets the characters array, forcing getcharacters to reset
|
||
characters.length = 0;
|
||
}
|
||
|
||
export function setMenuType(value) {
|
||
menu_type = value;
|
||
}
|
||
|
||
export function setExternalAbortController(controller) {
|
||
abortController = controller;
|
||
}
|
||
|
||
function setCharacterId(value) {
|
||
this_chid = value;
|
||
}
|
||
|
||
function setCharacterName(value) {
|
||
name2 = value;
|
||
}
|
||
|
||
function setOnlineStatus(value) {
|
||
online_status = value;
|
||
}
|
||
|
||
function setEditedMessageId(value) {
|
||
this_edit_mes_id = value;
|
||
}
|
||
|
||
function setSendButtonState(value) {
|
||
is_send_press = value;
|
||
}
|
||
|
||
function resultCheckStatusNovel() {
|
||
is_api_button_press_novel = false;
|
||
checkOnlineStatus();
|
||
$("#api_loading_novel").css("display", "none");
|
||
$("#api_button_novel").css("display", "inline-block");
|
||
}
|
||
|
||
async function renameCharacter() {
|
||
const oldAvatar = characters[this_chid].avatar;
|
||
const newValue = await callPopup('<h3>New name:</h3>', 'input', characters[this_chid].name);
|
||
|
||
if (newValue && newValue !== characters[this_chid].name) {
|
||
const body = JSON.stringify({ avatar_url: oldAvatar, new_name: newValue });
|
||
const response = await fetch('/renamecharacter', {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
body,
|
||
});
|
||
|
||
try {
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
const newAvatar = data.avatar;
|
||
|
||
// Replace tags list
|
||
renameTagKey(oldAvatar, newAvatar);
|
||
|
||
// Reload characters list
|
||
await getCharacters();
|
||
|
||
// Find newly renamed character
|
||
const newChId = characters.findIndex(c => c.avatar == data.avatar);
|
||
|
||
if (newChId !== -1) {
|
||
// Select the character after the renaming
|
||
this_chid = -1;
|
||
selectCharacterById(String(newChId));
|
||
|
||
// Async delay to update UI
|
||
await delay(1);
|
||
|
||
if (this_chid === -1) {
|
||
throw new Error('New character not selected');
|
||
}
|
||
|
||
// Also rename as a group member
|
||
await renameGroupMember(oldAvatar, newAvatar, newValue);
|
||
const renamePastChatsConfirm = await callPopup(`<h3>Character renamed!</h3>
|
||
<p>Past chats will still contain the old character name. Would you like to update the character name in previous chats as well?</p>
|
||
<i><b>Sprites folder (if any) should be renamed manually.</b></i>`, 'confirm');
|
||
|
||
if (renamePastChatsConfirm) {
|
||
await renamePastChats(newAvatar, newValue);
|
||
await reloadCurrentChat();
|
||
toastr.success('Character renamed and past chats updated!');
|
||
}
|
||
}
|
||
else {
|
||
throw new Error('Newly renamed character was lost?');
|
||
}
|
||
}
|
||
else {
|
||
throw new Error('Could not rename the character');
|
||
}
|
||
}
|
||
catch {
|
||
// Reloading to prevent data corruption
|
||
await callPopup('Something went wrong. The page will be reloaded.', 'text');
|
||
location.reload();
|
||
}
|
||
}
|
||
}
|
||
|
||
async function renamePastChats(newAvatar, newValue) {
|
||
const pastChats = await getPastCharacterChats();
|
||
|
||
for (const { file_name } of pastChats) {
|
||
try {
|
||
const fileNameWithoutExtension = file_name.replace('.jsonl', '');
|
||
const getChatResponse = await fetch('/getchat', {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({
|
||
ch_name: newValue,
|
||
file_name: fileNameWithoutExtension,
|
||
avatar_url: newAvatar,
|
||
}),
|
||
cache: 'no-cache',
|
||
});
|
||
|
||
if (getChatResponse.ok) {
|
||
const currentChat = await getChatResponse.json();
|
||
|
||
for (const message of currentChat) {
|
||
if (message.is_user || message.is_system || message.extra?.type == system_message_types.NARRATOR) {
|
||
continue;
|
||
}
|
||
|
||
if (message.name !== undefined) {
|
||
message.name = newValue;
|
||
}
|
||
}
|
||
|
||
const saveChatResponse = await fetch('/savechat', {
|
||
method: "POST",
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({
|
||
ch_name: newValue,
|
||
file_name: fileNameWithoutExtension,
|
||
chat: currentChat,
|
||
avatar_url: newAvatar,
|
||
}),
|
||
cache: 'no-cache',
|
||
});
|
||
|
||
if (!saveChatResponse.ok) {
|
||
throw new Error('Could not save chat');
|
||
}
|
||
}
|
||
} catch (error) {
|
||
toastr.error(`Past chat could not be updated: ${file_name}`);
|
||
console.error(error);
|
||
}
|
||
}
|
||
}
|
||
|
||
function saveChatDebounced() {
|
||
const chid = this_chid;
|
||
const selectedGroup = selected_group;
|
||
|
||
if (chatSaveTimeout) {
|
||
console.debug('Clearing chat save timeout');
|
||
clearTimeout(chatSaveTimeout);
|
||
}
|
||
|
||
chatSaveTimeout = setTimeout(async () => {
|
||
if (selectedGroup !== selected_group) {
|
||
console.warn('Chat save timeout triggered, but group changed. Aborting.');
|
||
return;
|
||
}
|
||
|
||
if (chid !== this_chid) {
|
||
console.warn('Chat save timeout triggered, but chid changed. Aborting.');
|
||
return;
|
||
}
|
||
|
||
console.debug('Chat save timeout triggered');
|
||
await saveChatConditional();
|
||
console.debug('Chat saved');
|
||
}, 1000);
|
||
}
|
||
|
||
async function saveChat(chat_name, withMetadata, mesId) {
|
||
const metadata = { ...chat_metadata, ...(withMetadata || {}) };
|
||
let file_name = chat_name ?? characters[this_chid].chat;
|
||
characters[this_chid]['date_last_chat'] = Date.now();
|
||
chat.forEach(function (item, i) {
|
||
if (item["is_group"]) {
|
||
toastr.error('Trying to save group chat with regular saveChat function. Aborting to prevent corruption.');
|
||
throw new Error('Group chat saved from saveChat');
|
||
}
|
||
/*
|
||
if (item.is_user) {
|
||
//var str = item.mes.replace(`${name1}:`, `${name1}:`);
|
||
//chat[i].mes = str;
|
||
//chat[i].name = name1;
|
||
} else if (i !== chat.length - 1 && chat[i].swipe_id !== undefined) {
|
||
// delete chat[i].swipes;
|
||
// delete chat[i].swipe_id;
|
||
}
|
||
*/
|
||
});
|
||
|
||
const trimmed_chat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
|
||
? chat.slice(0, parseInt(mesId) + 1)
|
||
: chat;
|
||
|
||
var save_chat = [
|
||
{
|
||
user_name: name1,
|
||
character_name: name2,
|
||
create_date: chat_create_date,
|
||
chat_metadata: metadata,
|
||
},
|
||
...trimmed_chat,
|
||
];
|
||
return jQuery.ajax({
|
||
type: "POST",
|
||
url: "/savechat",
|
||
data: JSON.stringify({
|
||
ch_name: characters[this_chid].name,
|
||
file_name: file_name,
|
||
chat: save_chat,
|
||
avatar_url: characters[this_chid].avatar,
|
||
}),
|
||
beforeSend: function () {
|
||
|
||
},
|
||
cache: false,
|
||
dataType: "json",
|
||
contentType: "application/json",
|
||
success: function (data) { },
|
||
error: function (jqXHR, exception) {
|
||
console.log(exception);
|
||
console.log(jqXHR);
|
||
},
|
||
});
|
||
}
|
||
|
||
async function read_avatar_load(input) {
|
||
if (input.files && input.files[0]) {
|
||
if (selected_button == "create") {
|
||
create_save.avatar = input.files;
|
||
}
|
||
|
||
const e = await new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.onload = resolve;
|
||
reader.onerror = reject;
|
||
reader.readAsDataURL(input.files[0]);
|
||
})
|
||
|
||
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
|
||
|
||
const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop');
|
||
if (!croppedImage) {
|
||
return;
|
||
}
|
||
|
||
$("#avatar_load_preview").attr("src", croppedImage || e.target.result);
|
||
|
||
if (menu_type == "create") {
|
||
return;
|
||
}
|
||
|
||
await createOrEditCharacter();
|
||
await delay(durationSaveEdit);
|
||
|
||
const formData = new FormData($("#form_create").get(0));
|
||
await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), {
|
||
method: 'GET',
|
||
cache: 'no-cache',
|
||
headers: {
|
||
'pragma': 'no-cache',
|
||
'cache-control': 'no-cache',
|
||
}
|
||
});
|
||
|
||
$(".mes").each(async function () {
|
||
const nameMatch = $(this).attr("ch_name") == formData.get('ch_name');
|
||
if ($(this).attr("is_system") == 'true' && !nameMatch) {
|
||
return;
|
||
}
|
||
if ($(this).attr("is_user") == 'true') {
|
||
return;
|
||
}
|
||
if (nameMatch) {
|
||
const previewSrc = $("#avatar_load_preview").attr("src");
|
||
const avatar = $(this).find(".avatar img");
|
||
avatar.attr('src', default_avatar);
|
||
await delay(1);
|
||
avatar.attr('src', previewSrc);
|
||
}
|
||
});
|
||
|
||
console.log('Avatar refreshed');
|
||
}
|
||
}
|
||
|
||
export function getCropPopup(src) {
|
||
return `<h3>Set the crop position of the avatar image and click Accept to confirm.</h3>
|
||
<div id='avatarCropWrap'>
|
||
<img id='avatarToCrop' src='${src}'>
|
||
</div>`;
|
||
}
|
||
|
||
function getThumbnailUrl(type, file) {
|
||
return `/thumbnail?type=${type}&file=${encodeURIComponent(file)}`;
|
||
}
|
||
|
||
async function getChat() {
|
||
//console.log('/getchat -- entered for -- ' + characters[this_chid].name);
|
||
try {
|
||
const response = await $.ajax({
|
||
type: 'POST',
|
||
url: '/getchat',
|
||
data: JSON.stringify({
|
||
ch_name: characters[this_chid].name,
|
||
file_name: characters[this_chid].chat,
|
||
avatar_url: characters[this_chid].avatar
|
||
}),
|
||
dataType: 'json',
|
||
contentType: 'application/json',
|
||
});
|
||
if (response[0] !== undefined) {
|
||
chat.push(...response);
|
||
chat_create_date = chat[0]['create_date'];
|
||
chat_metadata = chat[0]['chat_metadata'] ?? {};
|
||
|
||
chat.shift();
|
||
} else {
|
||
chat_create_date = humanizedDateTime();
|
||
}
|
||
await getChatResult();
|
||
eventSource.emit('chatLoaded', { detail: { id: this_chid, character: characters[this_chid] } });
|
||
|
||
setTimeout(function () {
|
||
$('#send_textarea').click();
|
||
$('#send_textarea').focus();
|
||
}, 200);
|
||
} catch (error) {
|
||
await getChatResult();
|
||
console.log(error);
|
||
}
|
||
}
|
||
|
||
async function getChatResult() {
|
||
name2 = characters[this_chid].name;
|
||
if (chat.length === 0) {
|
||
const message = getFirstMessage();
|
||
chat.push(message);
|
||
await saveChatConditional();
|
||
}
|
||
await printMessages();
|
||
select_selected_character(this_chid);
|
||
|
||
await eventSource.emit(event_types.CHAT_CHANGED, (getCurrentChatId()));
|
||
|
||
if (chat.length === 1) {
|
||
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
|
||
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
|
||
}
|
||
}
|
||
|
||
function getFirstMessage() {
|
||
const firstMes = characters[this_chid].first_mes || default_ch_mes;
|
||
const alternateGreetings = characters[this_chid]?.data?.alternate_greetings;
|
||
|
||
const message = {
|
||
name: name2,
|
||
is_user: false,
|
||
is_system: false,
|
||
send_date: getMessageTimeStamp(),
|
||
mes: getRegexedString(firstMes, regex_placement.AI_OUTPUT),
|
||
extra: {},
|
||
};
|
||
|
||
if (Array.isArray(alternateGreetings) && alternateGreetings.length > 0) {
|
||
const swipes = [message.mes, ...(alternateGreetings.map(greeting => substituteParams(getRegexedString(greeting, regex_placement.AI_OUTPUT))))];
|
||
message['swipe_id'] = 0;
|
||
message['swipes'] = swipes;
|
||
message['swipe_info'] = [];
|
||
}
|
||
return message;
|
||
}
|
||
|
||
async function openCharacterChat(file_name) {
|
||
characters[this_chid]["chat"] = file_name;
|
||
clearChat();
|
||
chat.length = 0;
|
||
chat_metadata = {};
|
||
await getChat();
|
||
$("#selected_chat_pole").val(file_name);
|
||
await createOrEditCharacter();
|
||
}
|
||
|
||
////////// OPTIMZED MAIN API CHANGE FUNCTION ////////////
|
||
|
||
function changeMainAPI() {
|
||
const selectedVal = $("#main_api").val();
|
||
//console.log(selectedVal);
|
||
const apiElements = {
|
||
"koboldhorde": {
|
||
apiSettings: $("#kobold_api-settings"),
|
||
apiConnector: $("#kobold_horde"),
|
||
apiPresets: $('#kobold_api-presets'),
|
||
apiRanges: $("#range_block"),
|
||
maxContextElem: $("#max_context_block"),
|
||
amountGenElem: $("#amount_gen_block"),
|
||
},
|
||
"kobold": {
|
||
apiSettings: $("#kobold_api-settings"),
|
||
apiConnector: $("#kobold_api"),
|
||
apiPresets: $('#kobold_api-presets'),
|
||
apiRanges: $("#range_block"),
|
||
maxContextElem: $("#max_context_block"),
|
||
amountGenElem: $("#amount_gen_block"),
|
||
},
|
||
"textgenerationwebui": {
|
||
apiSettings: $("#textgenerationwebui_api-settings"),
|
||
apiConnector: $("#textgenerationwebui_api"),
|
||
apiPresets: $('#textgenerationwebui_api-presets'),
|
||
apiRanges: $("#range_block_textgenerationwebui"),
|
||
maxContextElem: $("#max_context_block"),
|
||
amountGenElem: $("#amount_gen_block"),
|
||
},
|
||
"novel": {
|
||
apiSettings: $("#novel_api-settings"),
|
||
apiConnector: $("#novel_api"),
|
||
apiPresets: $('#novel_api-presets'),
|
||
apiRanges: $("#range_block_novel"),
|
||
maxContextElem: $("#max_context_block"),
|
||
amountGenElem: $("#amount_gen_block"),
|
||
},
|
||
"openai": {
|
||
apiSettings: $("#openai_settings"),
|
||
apiConnector: $("#openai_api"),
|
||
apiPresets: $('#openai_api-presets'),
|
||
apiRanges: $("#range_block_openai"),
|
||
maxContextElem: $("#max_context_block"),
|
||
amountGenElem: $("#amount_gen_block"),
|
||
}
|
||
};
|
||
//console.log('--- apiElements--- ');
|
||
//console.log(apiElements);
|
||
|
||
//first, disable everything so the old elements stop showing
|
||
for (const apiName in apiElements) {
|
||
const apiObj = apiElements[apiName];
|
||
//do not hide items to then proceed to immediately show them.
|
||
if (selectedVal === apiName) {
|
||
continue;
|
||
}
|
||
apiObj.apiSettings.css("display", "none");
|
||
apiObj.apiConnector.css("display", "none");
|
||
apiObj.apiRanges.css("display", "none");
|
||
apiObj.apiPresets.css("display", "none");
|
||
}
|
||
|
||
//then, find and enable the active item.
|
||
//This is split out of the loop so that different apis can share settings divs
|
||
let activeItem = apiElements[selectedVal];
|
||
|
||
activeItem.apiSettings.css("display", "block");
|
||
activeItem.apiConnector.css("display", "block");
|
||
activeItem.apiRanges.css("display", "block");
|
||
activeItem.apiPresets.css("display", "block");
|
||
|
||
if (selectedVal === "openai") {
|
||
activeItem.apiPresets.css("display", "flex");
|
||
}
|
||
|
||
if (selectedVal === "textgenerationwebui" || selectedVal === "novel") {
|
||
console.log("enabling amount_gen for ooba/novel");
|
||
activeItem.amountGenElem.find('input').prop("disabled", false);
|
||
activeItem.amountGenElem.css("opacity", 1.0);
|
||
}
|
||
|
||
if (selectedVal === "novel") {
|
||
$("#ai_module_block_novel").css("display", "block");
|
||
} else {
|
||
$("#ai_module_block_novel").css("display", "none");
|
||
}
|
||
|
||
// Hide common settings for OpenAI
|
||
console.debug('value?', selectedVal);
|
||
if (selectedVal == "openai") {
|
||
console.debug('hiding settings?');
|
||
$("#common-gen-settings-block").css("display", "none");
|
||
} else {
|
||
$("#common-gen-settings-block").css("display", "block");
|
||
}
|
||
|
||
main_api = selectedVal;
|
||
online_status = "no_connection";
|
||
|
||
if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||
$('#api_button_openai').trigger('click');
|
||
}
|
||
|
||
if (main_api == "koboldhorde") {
|
||
is_get_status = true;
|
||
getStatus();
|
||
getHordeModels();
|
||
}
|
||
|
||
switch (oai_settings.chat_completion_source) {
|
||
case chat_completion_sources.SCALE:
|
||
case chat_completion_sources.OPENROUTER:
|
||
case chat_completion_sources.WINDOWAI:
|
||
case chat_completion_sources.CLAUDE:
|
||
case chat_completion_sources.OPENAI:
|
||
case chat_completion_sources.AI21:
|
||
case chat_completion_sources.PALM:
|
||
default:
|
||
setupChatCompletionPromptManager(oai_settings);
|
||
break;
|
||
}
|
||
}
|
||
|
||
////////////////////////////////////////////////////
|
||
|
||
export async function getUserAvatars() {
|
||
const response = await fetch("/getuseravatars", {
|
||
method: "POST",
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({
|
||
"": "",
|
||
}),
|
||
});
|
||
if (response.ok === true) {
|
||
const getData = await response.json();
|
||
//background = getData;
|
||
//console.log(getData.length);
|
||
$("#user_avatar_block").html(""); //RossAscends: necessary to avoid doubling avatars each refresh.
|
||
$("#user_avatar_block").append('<div class="avatar_upload">+</div>');
|
||
|
||
for (var i = 0; i < getData.length; i++) {
|
||
//console.log(1);
|
||
appendUserAvatar(getData[i]);
|
||
}
|
||
//var aa = JSON.parse(getData[0]);
|
||
//const load_ch_coint = Object.getOwnPropertyNames(getData);
|
||
}
|
||
}
|
||
|
||
function highlightSelectedAvatar() {
|
||
$("#user_avatar_block").find(".avatar").removeClass("selected");
|
||
$("#user_avatar_block")
|
||
.find(`.avatar[imgfile='${user_avatar}']`)
|
||
.addClass("selected");
|
||
}
|
||
|
||
function appendUserAvatar(name) {
|
||
const template = $('#user_avatar_template .avatar-container').clone();
|
||
const personaName = power_user.personas[name];
|
||
if (personaName) {
|
||
template.attr('title', personaName);
|
||
} else {
|
||
template.attr('title', '[Unnamed Persona]');
|
||
}
|
||
template.find('.avatar').attr('imgfile', name);
|
||
template.toggleClass('default_persona', name === power_user.default_persona)
|
||
template.find('img').attr('src', getUserAvatar(name));
|
||
$("#user_avatar_block").append(template);
|
||
highlightSelectedAvatar();
|
||
}
|
||
|
||
function reloadUserAvatar(force = false) {
|
||
$(".mes").each(function () {
|
||
const avatarImg = $(this).find(".avatar img");
|
||
if (force) {
|
||
avatarImg.attr("src", avatarImg.attr("src"));
|
||
}
|
||
|
||
if ($(this).attr("is_user") == 'true' && $(this).attr('force_avatar') == 'false') {
|
||
avatarImg.attr("src", getUserAvatar(user_avatar));
|
||
}
|
||
});
|
||
}
|
||
|
||
export function setUserName(value) {
|
||
name1 = value;
|
||
if (name1 === undefined || name1 == "")
|
||
name1 = default_user_name;
|
||
console.log(`User name changed to ${name1}`);
|
||
$("#your_name").val(name1);
|
||
if (power_user.persona_show_notifications) {
|
||
toastr.success(`Your messages will now be sent as ${name1}`, 'Current persona updated');
|
||
}
|
||
saveSettings("change_name");
|
||
}
|
||
|
||
function setUserAvatar() {
|
||
user_avatar = $(this).attr("imgfile");
|
||
reloadUserAvatar();
|
||
saveSettingsDebounced();
|
||
highlightSelectedAvatar();
|
||
selectCurrentPersona();
|
||
$('.zoomed_avatar[forchar]').remove();
|
||
}
|
||
|
||
async function uploadUserAvatar(e) {
|
||
const file = e.target.files[0];
|
||
|
||
if (!file) {
|
||
$("#form_upload_avatar").trigger("reset");
|
||
return;
|
||
}
|
||
|
||
const formData = new FormData($("#form_upload_avatar").get(0));
|
||
|
||
const dataUrl = await new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.onload = resolve;
|
||
reader.onerror = reject;
|
||
reader.readAsDataURL(file);
|
||
});
|
||
|
||
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
|
||
const confirmation = await callPopup(getCropPopup(dataUrl.target.result), 'avatarToCrop');
|
||
if (!confirmation) {
|
||
return;
|
||
}
|
||
|
||
let url = "/uploaduseravatar";
|
||
|
||
if (crop_data !== undefined) {
|
||
url += `?crop=${encodeURIComponent(JSON.stringify(crop_data))}`;
|
||
}
|
||
|
||
jQuery.ajax({
|
||
type: "POST",
|
||
url: url,
|
||
data: formData,
|
||
beforeSend: () => { },
|
||
cache: false,
|
||
contentType: false,
|
||
processData: false,
|
||
success: async function () {
|
||
// If the user uploaded a new avatar, we want to make sure it's not cached
|
||
const name = formData.get("overwrite_name");
|
||
if (name) {
|
||
await fetch(getUserAvatar(name), { cache: "no-cache" });
|
||
reloadUserAvatar(true);
|
||
}
|
||
|
||
crop_data = undefined;
|
||
await getUserAvatars();
|
||
},
|
||
error: (jqXHR, exception) => { },
|
||
});
|
||
|
||
// Will allow to select the same file twice in a row
|
||
$("#form_upload_avatar").trigger("reset");
|
||
}
|
||
|
||
async function doOnboarding(avatarId) {
|
||
let simpleUiMode = false;
|
||
const template = $('#onboarding_template .onboarding');
|
||
template.find('input[name="enable_simple_mode"]').on('input', function () {
|
||
simpleUiMode = $(this).is(':checked');
|
||
});
|
||
const userName = await callPopup(template, 'input', name1);
|
||
|
||
if (userName) {
|
||
setUserName(userName);
|
||
console.log(`Binding persona ${avatarId} to name ${userName}`);
|
||
power_user.personas[avatarId] = userName;
|
||
power_user.persona_descriptions[avatarId] = {
|
||
description: '',
|
||
position: persona_description_positions.IN_PROMPT,
|
||
};
|
||
}
|
||
|
||
if (simpleUiMode) {
|
||
power_user.ui_mode = ui_mode.SIMPLE;
|
||
$('#ui_mode_select').val(power_user.ui_mode);
|
||
switchSimpleMode();
|
||
}
|
||
}
|
||
|
||
//***************SETTINGS****************//
|
||
///////////////////////////////////////////
|
||
async function getSettings(type) {
|
||
const response = await fetch("/getsettings", {
|
||
method: "POST",
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({}),
|
||
cache: "no-cache",
|
||
});
|
||
|
||
if (!response.ok) {
|
||
toastr.error('Settings could not be loaded. Try reloading the page.');
|
||
throw new Error('Error getting settings');
|
||
}
|
||
|
||
const data = await response.json();
|
||
if (data.result != "file not find" && data.settings) {
|
||
settings = JSON.parse(data.settings);
|
||
if (settings.username !== undefined && settings.username !== "") {
|
||
name1 = settings.username;
|
||
$("#your_name").val(name1);
|
||
}
|
||
|
||
// Allow subscribers to mutate settings
|
||
eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings);
|
||
|
||
//Load KoboldAI settings
|
||
koboldai_setting_names = data.koboldai_setting_names;
|
||
koboldai_settings = data.koboldai_settings;
|
||
koboldai_settings.forEach(function (item, i, arr) {
|
||
koboldai_settings[i] = JSON.parse(item);
|
||
});
|
||
|
||
let arr_holder = {};
|
||
|
||
$("#settings_perset").empty(); //RossAscends: uncommented this to prevent settings selector from doubling preset list on refresh
|
||
$("#settings_perset").append(
|
||
'<option value="gui">GUI KoboldAI Settings</option>'
|
||
); //adding in the GUI settings, since it is not loaded dynamically
|
||
|
||
koboldai_setting_names.forEach(function (item, i, arr) {
|
||
arr_holder[item] = i;
|
||
$("#settings_perset").append(`<option value=${i}>${item}</option>`);
|
||
//console.log('loading preset #'+i+' -- '+item);
|
||
});
|
||
koboldai_setting_names = {};
|
||
koboldai_setting_names = arr_holder;
|
||
preset_settings = settings.preset_settings;
|
||
|
||
if (preset_settings == "gui") {
|
||
selectKoboldGuiPreset();
|
||
} else {
|
||
if (typeof koboldai_setting_names[preset_settings] !== "undefined") {
|
||
$(`#settings_perset option[value=${koboldai_setting_names[preset_settings]}]`)
|
||
.attr("selected", "true");
|
||
} else {
|
||
preset_settings = "gui";
|
||
selectKoboldGuiPreset();
|
||
}
|
||
}
|
||
|
||
novelai_setting_names = data.novelai_setting_names;
|
||
novelai_settings = data.novelai_settings;
|
||
novelai_settings.forEach(function (item, i, arr) {
|
||
novelai_settings[i] = JSON.parse(item);
|
||
});
|
||
arr_holder = {};
|
||
|
||
$("#settings_perset_novel").empty();
|
||
|
||
novelai_setting_names.forEach(function (item, i, arr) {
|
||
arr_holder[item] = i;
|
||
$("#settings_perset_novel").append(`<option value=${i}>${item}</option>`);
|
||
});
|
||
novelai_setting_names = {};
|
||
novelai_setting_names = arr_holder;
|
||
|
||
//Load AI model config settings
|
||
|
||
amount_gen = settings.amount_gen;
|
||
if (settings.max_context !== undefined)
|
||
max_context = parseInt(settings.max_context);
|
||
|
||
swipes = settings.swipes !== undefined ? !!settings.swipes : true; // enable swipes by default
|
||
$('#swipes-checkbox').prop('checked', swipes); /// swipecode
|
||
hideSwipeButtons();
|
||
showSwipeButtons();
|
||
|
||
// Kobold
|
||
loadKoboldSettings(settings.kai_settings ?? settings);
|
||
|
||
// Novel
|
||
loadNovelSettings(settings.nai_settings ?? settings);
|
||
$(`#settings_perset_novel option[value=${novelai_setting_names[nai_settings.preset_settings_novel]}]`).attr("selected", "true");
|
||
|
||
// TextGen
|
||
loadTextGenSettings(data, settings);
|
||
|
||
// OpenAI
|
||
loadOpenAISettings(data, settings.oai_settings ?? settings);
|
||
|
||
// Horde
|
||
loadHordeSettings(settings);
|
||
|
||
// Load power user settings
|
||
loadPowerUserSettings(settings, data);
|
||
|
||
// Load character tags
|
||
loadTagsSettings(settings);
|
||
|
||
// Allow subscribers to mutate settings
|
||
eventSource.emit(event_types.SETTINGS_LOADED_AFTER, settings);
|
||
|
||
// Set context size after loading power user (may override the max value)
|
||
$("#max_context").val(max_context);
|
||
$("#max_context_counter").text(`${max_context}`);
|
||
|
||
$("#amount_gen").val(amount_gen);
|
||
$("#amount_gen_counter").text(`${amount_gen}`);
|
||
|
||
//Load which API we are using
|
||
if (settings.main_api == undefined) {
|
||
settings.main_api = 'kobold';
|
||
}
|
||
|
||
if (settings.main_api == 'poe') {
|
||
settings.main_api = 'openai';
|
||
}
|
||
|
||
main_api = settings.main_api;
|
||
$('#main_api').val(main_api);
|
||
$("#main_api option[value=" + main_api + "]").attr(
|
||
"selected",
|
||
"true"
|
||
);
|
||
changeMainAPI();
|
||
|
||
//Load User's Name and Avatar
|
||
|
||
user_avatar = settings.user_avatar;
|
||
firstRun = !!settings.firstRun;
|
||
|
||
if (firstRun) {
|
||
await doOnboarding(user_avatar);
|
||
firstRun = false;
|
||
}
|
||
|
||
reloadUserAvatar();
|
||
highlightSelectedAvatar();
|
||
setPersonaDescription();
|
||
|
||
//Load the active character and group
|
||
active_character = settings.active_character;
|
||
active_group = settings.active_group;
|
||
|
||
//Load the API server URL from settings
|
||
api_server = settings.api_server;
|
||
$("#api_url_text").val(api_server);
|
||
|
||
setWorldInfoSettings(settings.world_info_settings ?? settings, data);
|
||
|
||
api_server_textgenerationwebui = settings.api_server_textgenerationwebui;
|
||
$("#textgenerationwebui_api_url_text").val(api_server_textgenerationwebui);
|
||
$("#mancer_api_url_text").val(api_server_textgenerationwebui);
|
||
api_use_mancer_webui = settings.api_use_mancer_webui
|
||
$('#use-mancer-api-checkbox').prop("checked", api_use_mancer_webui);
|
||
$('#use-mancer-api-checkbox').trigger("change");
|
||
|
||
selected_button = settings.selected_button;
|
||
|
||
if (data.enable_extensions) {
|
||
await loadExtensionSettings(settings);
|
||
eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
|
||
}
|
||
}
|
||
|
||
eventSource.emit(event_types.SETTINGS_LOADED);
|
||
}
|
||
|
||
function selectKoboldGuiPreset() {
|
||
$("#settings_perset option[value=gui]")
|
||
.attr("selected", "true")
|
||
.trigger("change");
|
||
}
|
||
|
||
async function saveSettings(type) {
|
||
if (!settingsReady) {
|
||
console.warn('Settings not ready, aborting save');
|
||
return;
|
||
}
|
||
//console.log('Entering settings with name1 = '+name1);
|
||
|
||
return jQuery.ajax({
|
||
type: "POST",
|
||
url: "/savesettings",
|
||
data: JSON.stringify({
|
||
firstRun: firstRun,
|
||
username: name1,
|
||
active_character: active_character,
|
||
active_group: active_group,
|
||
api_server: api_server,
|
||
api_server_textgenerationwebui: api_server_textgenerationwebui,
|
||
api_use_mancer_webui: api_use_mancer_webui,
|
||
preset_settings: preset_settings,
|
||
user_avatar: user_avatar,
|
||
amount_gen: amount_gen,
|
||
max_context: max_context,
|
||
main_api: main_api,
|
||
world_info_settings: getWorldInfoSettings(),
|
||
textgenerationwebui_settings: textgenerationwebui_settings,
|
||
swipes: swipes,
|
||
horde_settings: horde_settings,
|
||
power_user: power_user,
|
||
extension_settings: extension_settings,
|
||
tags: tags,
|
||
tag_map: tag_map,
|
||
nai_settings: nai_settings,
|
||
kai_settings: kai_settings,
|
||
oai_settings: oai_settings,
|
||
}, null, 4),
|
||
beforeSend: function () {
|
||
if (type == "change_name") {
|
||
//let nameBeforeChange = name1;
|
||
name1 = $("#your_name").val();
|
||
//$(`.mes[ch_name="${nameBeforeChange}"]`).attr('ch_name' === name1);
|
||
//console.log('beforeSend name1 = ' + nameBeforeChange);
|
||
//console.log('new name: ' + name1);
|
||
}
|
||
},
|
||
cache: false,
|
||
dataType: "json",
|
||
contentType: "application/json",
|
||
//processData: false,
|
||
success: async function (data) {
|
||
//online_status = data.result;
|
||
eventSource.emit(event_types.SETTINGS_UPDATED);
|
||
if (type == "change_name") {
|
||
clearChat();
|
||
await printMessages();
|
||
}
|
||
},
|
||
error: function (jqXHR, exception) {
|
||
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Settings could not be saved');
|
||
console.log(exception);
|
||
console.log(jqXHR);
|
||
},
|
||
});
|
||
}
|
||
|
||
export function setGenerationParamsFromPreset(preset) {
|
||
if (preset.genamt !== undefined) {
|
||
amount_gen = preset.genamt;
|
||
$("#amount_gen").val(amount_gen);
|
||
$("#amount_gen_counter").text(`${amount_gen}`);
|
||
}
|
||
|
||
if (preset.max_length !== undefined) {
|
||
const needsUnlock = preset.max_length > MAX_CONTEXT_DEFAULT;
|
||
$('#max_context_unlocked').prop('checked', needsUnlock).trigger('change');
|
||
max_context = preset.max_length;
|
||
|
||
$("#max_context").val(max_context);
|
||
$("#max_context_counter").text(`${max_context}`);
|
||
}
|
||
}
|
||
|
||
// Common code for message editor done and auto-save
|
||
function updateMessage(div) {
|
||
const mesBlock = div.closest(".mes_block");
|
||
let text = mesBlock.find(".edit_textarea").val();
|
||
const mes = chat[this_edit_mes_id];
|
||
|
||
let regexPlacement;
|
||
if (mes.is_user) {
|
||
regexPlacement = regex_placement.USER_INPUT;
|
||
} else if (mes.name === name2) {
|
||
regexPlacement = regex_placement.AI_OUTPUT;
|
||
} else if (mes.name !== name2 || mes.extra?.type === "narrator") {
|
||
regexPlacement = regex_placement.SLASH_COMMAND;
|
||
}
|
||
|
||
// Ignore character override if sent as system
|
||
text = getRegexedString(
|
||
text,
|
||
regexPlacement,
|
||
{ characterOverride: mes.extra?.type === "narrator" ? undefined : mes.name }
|
||
);
|
||
|
||
|
||
if (power_user.trim_spaces) {
|
||
text = text.trim();
|
||
}
|
||
|
||
const bias = extractMessageBias(text);
|
||
mes["mes"] = text;
|
||
if (mes["swipe_id"] !== undefined) {
|
||
mes["swipes"][mes["swipe_id"]] = text;
|
||
}
|
||
|
||
// editing old messages
|
||
if (!mes.extra) {
|
||
mes.extra = {};
|
||
}
|
||
|
||
if (mes.is_system || mes.is_user || mes.extra.type === system_message_types.NARRATOR) {
|
||
mes.extra.bias = bias ?? null;
|
||
} else {
|
||
mes.extra.bias = null;
|
||
}
|
||
|
||
return { mesBlock, text, mes, bias };
|
||
}
|
||
|
||
function openMessageDelete() {
|
||
closeMessageEditor();
|
||
hideSwipeButtons();
|
||
if ((this_chid != undefined && !is_send_press) || (selected_group && !is_group_generating)) {
|
||
$("#dialogue_del_mes").css("display", "block");
|
||
$("#send_form").css("display", "none");
|
||
$(".del_checkbox").each(function () {
|
||
if ($(this).parent().attr("mesid") != 0) {
|
||
$(this).css("display", "block");
|
||
$(this).parent().children(".for_checkbox").css("display", "none");
|
||
}
|
||
});
|
||
} else {
|
||
console.debug(`
|
||
ERR -- could not enter del mode
|
||
this_chid: ${this_chid}
|
||
is_send_press: ${is_send_press}
|
||
selected_group: ${selected_group}
|
||
is_group_generating: ${is_group_generating}`);
|
||
}
|
||
is_delete_mode = true;
|
||
}
|
||
|
||
function messageEditAuto(div) {
|
||
const { mesBlock, text, mes } = updateMessage(div);
|
||
|
||
mesBlock.find(".mes_text").val('');
|
||
mesBlock.find(".mes_text").val(messageFormatting(
|
||
text,
|
||
this_edit_mes_chname,
|
||
mes.is_system,
|
||
mes.is_user,
|
||
));
|
||
saveChatDebounced();
|
||
}
|
||
|
||
async function messageEditDone(div) {
|
||
let { mesBlock, text, mes, bias } = updateMessage(div);
|
||
if (this_edit_mes_id == 0) {
|
||
text = substituteParams(text);
|
||
}
|
||
|
||
mesBlock.find(".mes_text").empty();
|
||
mesBlock.find(".mes_edit_buttons").css("display", "none");
|
||
mesBlock.find(".mes_buttons").css("display", "");
|
||
mesBlock.find(".mes_text").append(
|
||
messageFormatting(
|
||
text,
|
||
this_edit_mes_chname,
|
||
mes.is_system,
|
||
mes.is_user,
|
||
)
|
||
);
|
||
mesBlock.find(".mes_bias").empty();
|
||
mesBlock.find(".mes_bias").append(messageFormatting(bias));
|
||
appendImageToMessage(mes, div.closest(".mes"));
|
||
addCopyToCodeBlocks(div.closest(".mes"));
|
||
await eventSource.emit(event_types.MESSAGE_EDITED, this_edit_mes_id);
|
||
|
||
this_edit_mes_id = undefined;
|
||
await saveChatConditional();
|
||
}
|
||
|
||
/**
|
||
* Fetches the chat content for each chat file from the server and compiles them into a dictionary.
|
||
* The function iterates over a provided list of chat metadata and requests the actual chat content
|
||
* for each chat, either as an individual chat or a group chat based on the context.
|
||
*
|
||
* @param {Array} data - An array containing metadata about each chat such as file_name.
|
||
* @param {boolean} isGroupChat - A flag indicating if the chat is a group chat.
|
||
* @returns {Promise<Object>} chat_dict - A dictionary where each key is a file_name and the value is the
|
||
* corresponding chat content fetched from the server.
|
||
*/
|
||
export async function getChatsFromFiles(data, isGroupChat) {
|
||
const context = getContext();
|
||
let chat_dict = {};
|
||
let chat_list = Object.values(data).sort((a, b) => a["file_name"].localeCompare(b["file_name"])).reverse();
|
||
|
||
let chat_promise = chat_list.map(({ file_name }) => {
|
||
return new Promise(async (res, rej) => {
|
||
try {
|
||
const endpoint = isGroupChat ? '/getgroupchat' : '/getchat';
|
||
const requestBody = isGroupChat
|
||
? JSON.stringify({ id: file_name })
|
||
: JSON.stringify({
|
||
ch_name: characters[context.characterId].name,
|
||
file_name: file_name.replace('.jsonl', ''),
|
||
avatar_url: characters[context.characterId].avatar
|
||
});
|
||
|
||
const chatResponse = await fetch(endpoint, {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
body: requestBody,
|
||
cache: 'no-cache',
|
||
});
|
||
|
||
if (!chatResponse.ok) {
|
||
return res();
|
||
// continue;
|
||
}
|
||
|
||
const currentChat = await chatResponse.json();
|
||
if (!isGroupChat) {
|
||
// remove the first message, which is metadata, only for individual chats
|
||
currentChat.shift();
|
||
}
|
||
chat_dict[file_name] = currentChat;
|
||
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
|
||
return res();
|
||
})
|
||
})
|
||
|
||
await Promise.all(chat_promise)
|
||
|
||
return chat_dict;
|
||
}
|
||
|
||
/**
|
||
* Fetches the metadata of all past chats related to a specific character based on its avatar URL.
|
||
* The function sends a POST request to the server to retrieve all chats for the character. It then
|
||
* processes the received data, sorts it by the file name, and returns the sorted data.
|
||
*
|
||
* @returns {Promise<Array>} - An array containing metadata of all past chats of the character, sorted
|
||
* in descending order by file name. Returns `undefined` if the fetch request is unsuccessful.
|
||
*/
|
||
async function getPastCharacterChats() {
|
||
const response = await fetch("/getallchatsofcharacter", {
|
||
method: 'POST',
|
||
body: JSON.stringify({ avatar_url: characters[this_chid].avatar }),
|
||
headers: getRequestHeaders(),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
return;
|
||
}
|
||
|
||
let data = await response.json();
|
||
data = Object.values(data);
|
||
data = data.sort((a, b) => a["file_name"].localeCompare(b["file_name"])).reverse();
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* Displays the past chats for a character or a group based on the selected context.
|
||
* The function first fetches the chats, processes them, and then displays them in
|
||
* the HTML. It also has a built-in search functionality that allows filtering the
|
||
* displayed chats based on a search query.
|
||
*/
|
||
export async function displayPastChats() {
|
||
$("#select_chat_div").empty();
|
||
|
||
const group = selected_group ? groups.find(x => x.id === selected_group) : null;
|
||
const data = await (selected_group ? getGroupPastChats(selected_group) : getPastCharacterChats());
|
||
const currentChat = selected_group ? group?.chat_id : characters[this_chid]["chat"];
|
||
const displayName = selected_group ? group?.name : characters[this_chid].name;
|
||
const avatarImg = selected_group ? group?.avatar_url : getThumbnailUrl('avatar', characters[this_chid]['avatar']);
|
||
const rawChats = await getChatsFromFiles(data, selected_group);
|
||
// Sort by last message date descending
|
||
data.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes)));
|
||
console.log(data);
|
||
$("#load_select_chat_div").css("display", "none");
|
||
$("#ChatHistoryCharName").text(displayName);
|
||
|
||
const displayChats = (searchQuery) => {
|
||
$("#select_chat_div").empty(); // Clear the current chats before appending filtered chats
|
||
|
||
const filteredData = data.filter(chat => {
|
||
const fileName = chat['file_name'];
|
||
const chatContent = rawChats[fileName];
|
||
|
||
return chatContent && Object.values(chatContent).some(message => message?.mes?.toLowerCase()?.includes(searchQuery.toLowerCase()));
|
||
});
|
||
|
||
console.log(filteredData);
|
||
for (const key in filteredData) {
|
||
let strlen = 300;
|
||
let mes = filteredData[key]["mes"];
|
||
|
||
if (mes !== undefined) {
|
||
if (mes.length > strlen) {
|
||
mes = "..." + mes.substring(mes.length - strlen);
|
||
}
|
||
const chat_items = data[key]["chat_items"];
|
||
const file_size = data[key]["file_size"];
|
||
const fileName = data[key]['file_name'];
|
||
const timestamp = timestampToMoment(data[key]['last_mes']).format('LL LT');
|
||
const template = $('#past_chat_template .select_chat_block_wrapper').clone();
|
||
template.find('.select_chat_block').attr('file_name', fileName);
|
||
template.find('.avatar img').attr('src', avatarImg);
|
||
template.find('.select_chat_block_filename').text(fileName);
|
||
template.find('.chat_file_size').text(" (" + file_size + ")");
|
||
template.find('.chat_messages_num').text(" (" + chat_items + " messages)");
|
||
template.find('.select_chat_block_mes').text(mes);
|
||
template.find('.PastChat_cross').attr('file_name', fileName);
|
||
template.find('.chat_messages_date').text(timestamp);
|
||
|
||
if (selected_group) {
|
||
template.find('.avatar img').replaceWith(getGroupAvatar(group));
|
||
}
|
||
|
||
$("#select_chat_div").append(template);
|
||
|
||
if (currentChat === fileName.toString().replace(".jsonl", "")) {
|
||
$("#select_chat_div").find(".select_chat_block:last").attr("highlight", true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
displayChats(''); // Display all by default
|
||
|
||
const debouncedDisplay = debounce((searchQuery) => {
|
||
displayChats(searchQuery);
|
||
}, 300);
|
||
|
||
// Define the search input listener
|
||
$("#select_chat_search").on("input", function () {
|
||
const searchQuery = $(this).val();
|
||
debouncedDisplay(searchQuery);
|
||
});
|
||
}
|
||
|
||
//************************************************************
|
||
//************************Novel.AI****************************
|
||
//************************************************************
|
||
async function getStatusNovel() {
|
||
if (is_get_status_novel) {
|
||
try {
|
||
const result = await loadNovelSubscriptionData();
|
||
|
||
if (!result) {
|
||
throw new Error('Could not load subscription data');
|
||
}
|
||
|
||
online_status = getNovelTier();
|
||
} catch {
|
||
online_status = "no_connection";
|
||
}
|
||
|
||
resultCheckStatusNovel();
|
||
} else {
|
||
if (is_get_status != true && is_get_status_openai != true) {
|
||
online_status = "no_connection";
|
||
}
|
||
}
|
||
}
|
||
|
||
function selectRightMenuWithAnimation(selectedMenuId) {
|
||
const displayModes = {
|
||
'rm_group_chats_block': 'flex',
|
||
'rm_api_block': 'grid',
|
||
'rm_characters_block': 'flex',
|
||
};
|
||
$('#result_info').toggle(selectedMenuId === 'rm_ch_create_block');
|
||
document.querySelectorAll('#right-nav-panel .right_menu').forEach((menu) => {
|
||
$(menu).css('display', 'none');
|
||
|
||
if (selectedMenuId && selectedMenuId.replace('#', '') === menu.id) {
|
||
const mode = displayModes[menu.id] ?? 'block';
|
||
$(menu).css('display', mode);
|
||
$(menu).css("opacity", 0.0);
|
||
$(menu).transition({
|
||
opacity: 1.0,
|
||
duration: animation_duration,
|
||
easing: animation_easing,
|
||
complete: function () { },
|
||
});
|
||
}
|
||
})
|
||
}
|
||
|
||
function select_rm_info(type, charId, previousCharId = null) {
|
||
if (!type) {
|
||
toastr.error(`Invalid process (no 'type')`);
|
||
return;
|
||
}
|
||
if (type !== 'group_create') {
|
||
var displayName = String(charId).replace('.png', '');
|
||
}
|
||
|
||
if (type === 'char_delete') {
|
||
toastr.warning(`Character Deleted: ${displayName}`);
|
||
}
|
||
if (type === 'char_create') {
|
||
toastr.success(`Character Created: ${displayName}`);
|
||
}
|
||
if (type === 'group_create') {
|
||
toastr.success(`Group Created`);
|
||
}
|
||
if (type === 'group_delete') {
|
||
toastr.warning(`Group Deleted`);
|
||
}
|
||
|
||
if (type === 'char_import') {
|
||
toastr.success(`Character Imported: ${displayName}`);
|
||
}
|
||
|
||
selectRightMenuWithAnimation('rm_characters_block');
|
||
|
||
// Set a timeout so multiple flashes don't overlap
|
||
clearTimeout(importFlashTimeout);
|
||
importFlashTimeout = setTimeout(function () {
|
||
if (type === 'char_import' || type === 'char_create') {
|
||
// Find the page at which the character is located
|
||
const charData = getEntitiesList({ doFilter: true });
|
||
const charIndex = charData.findIndex((x) => x?.item?.avatar?.startsWith(charId));
|
||
|
||
if (charIndex === -1) {
|
||
console.log(`Could not find character ${charId} in the list`);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const perPage = Number(localStorage.getItem('Characters_PerPage'));
|
||
const page = Math.floor(charIndex / perPage) + 1;
|
||
const selector = `#rm_print_characters_block [title^="${charId}"]`;
|
||
$('#rm_print_characters_pagination').pagination('go', page);
|
||
|
||
waitUntilCondition(() => document.querySelector(selector) !== null).then(() => {
|
||
const element = $(selector).parent();
|
||
|
||
if (element.length === 0) {
|
||
console.log(`Could not find element for character ${charId}`);
|
||
return;
|
||
}
|
||
|
||
const scrollOffset = element.offset().top - element.parent().offset().top;
|
||
element.parent().scrollTop(scrollOffset);
|
||
element.addClass('flash animated');
|
||
setTimeout(function () {
|
||
element.removeClass('flash animated');
|
||
}, 5000);
|
||
});
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
}
|
||
|
||
if (type === 'group_create') {
|
||
// Find the page at which the character is located
|
||
const charData = getEntitiesList({ doFilter: true });
|
||
const charIndex = charData.findIndex((x) => String(x?.item?.id) === String(charId));
|
||
|
||
if (charIndex === -1) {
|
||
console.log(`Could not find group ${charId} in the list`);
|
||
return;
|
||
}
|
||
|
||
const perPage = Number(localStorage.getItem('Characters_PerPage'));
|
||
const page = Math.floor(charIndex / perPage) + 1;
|
||
$('#rm_print_characters_pagination').pagination('go', page);
|
||
const selector = `#rm_print_characters_block [grid="${charId}"]`;
|
||
try {
|
||
waitUntilCondition(() => document.querySelector(selector) !== null).then(() => {
|
||
const element = $(selector);
|
||
const scrollOffset = element.offset().top - element.parent().offset().top;
|
||
element.parent().scrollTop(scrollOffset);
|
||
$(element).addClass('flash animated');
|
||
setTimeout(function () {
|
||
$(element).removeClass('flash animated');
|
||
}, 5000);
|
||
});
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
}
|
||
}, 250);
|
||
|
||
if (previousCharId) {
|
||
const newId = characters.findIndex((x) => x.avatar == previousCharId);
|
||
if (newId >= 0) {
|
||
this_chid = newId;
|
||
}
|
||
}
|
||
}
|
||
|
||
export function select_selected_character(chid) {
|
||
//character select
|
||
//console.log('select_selected_character() -- starting with input of -- '+chid+' (name:'+characters[chid].name+')');
|
||
select_rm_create();
|
||
menu_type = "character_edit";
|
||
$("#delete_button").css("display", "flex");
|
||
$("#export_button").css("display", "flex");
|
||
var display_name = characters[chid].name;
|
||
|
||
//create text poles
|
||
$("#rm_button_back").css("display", "none");
|
||
//$("#character_import_button").css("display", "none");
|
||
$("#create_button").attr("value", "Save"); // what is the use case for this?
|
||
$("#dupe_button").show();
|
||
$("#create_button_label").css("display", "none");
|
||
|
||
// Hide the chat scenario button if we're peeking the group member defs
|
||
$('#set_chat_scenario').toggle(!selected_group);
|
||
|
||
// Don't update the navbar name if we're peeking the group member defs
|
||
if (!selected_group) {
|
||
$("#rm_button_selected_ch").children("h2").text(display_name);
|
||
}
|
||
|
||
$("#add_avatar_button").val("");
|
||
|
||
$("#character_popup_text_h3").text(characters[chid].name);
|
||
$("#character_name_pole").val(characters[chid].name);
|
||
$("#description_textarea").val(characters[chid].description);
|
||
$("#character_world").val(characters[chid].data?.extensions?.world || '');
|
||
$("#creator_notes_textarea").val(characters[chid].data?.creator_notes || characters[chid].creatorcomment);
|
||
$("#creator_notes_spoiler").text(characters[chid].data?.creator_notes || characters[chid].creatorcomment);
|
||
$("#character_version_textarea").val(characters[chid].data?.character_version || '');
|
||
$("#system_prompt_textarea").val(characters[chid].data?.system_prompt || '');
|
||
$("#post_history_instructions_textarea").val(characters[chid].data?.post_history_instructions || '');
|
||
$("#tags_textarea").val(Array.isArray(characters[chid].data?.tags) ? characters[chid].data.tags.join(', ') : '');
|
||
$("#creator_textarea").val(characters[chid].data?.creator);
|
||
$("#character_version_textarea").val(characters[chid].data?.character_version || '');
|
||
$("#personality_textarea").val(characters[chid].personality);
|
||
$("#firstmessage_textarea").val(characters[chid].first_mes);
|
||
$("#scenario_pole").val(characters[chid].scenario);
|
||
$("#talkativeness_slider").val(characters[chid].talkativeness || talkativeness_default);
|
||
$("#mes_example_textarea").val(characters[chid].mes_example);
|
||
$("#selected_chat_pole").val(characters[chid].chat);
|
||
$("#create_date_pole").val(characters[chid].create_date);
|
||
$("#avatar_url_pole").val(characters[chid].avatar);
|
||
$("#chat_import_avatar_url").val(characters[chid].avatar);
|
||
$("#chat_import_character_name").val(characters[chid].name);
|
||
$("#character_json_data").val(characters[chid].json_data);
|
||
let this_avatar = default_avatar;
|
||
if (characters[chid].avatar != "none") {
|
||
this_avatar = getThumbnailUrl('avatar', characters[chid].avatar);
|
||
}
|
||
|
||
updateFavButtonState(characters[chid].fav || characters[chid].fav == "true");
|
||
|
||
$("#avatar_load_preview").attr("src", this_avatar);
|
||
$("#name_div").removeClass('displayBlock');
|
||
$("#name_div").addClass('displayNone');
|
||
$("#renameCharButton").css("display", "");
|
||
$('.open_alternate_greetings').data('chid', chid);
|
||
$('#set_character_world').data('chid', chid);
|
||
setWorldInfoButtonClass(chid);
|
||
checkEmbeddedWorld(chid);
|
||
|
||
$("#form_create").attr("actiontype", "editcharacter");
|
||
saveSettingsDebounced();
|
||
}
|
||
|
||
function select_rm_create() {
|
||
menu_type = "create";
|
||
|
||
//console.log('select_rm_Create() -- selected button: '+selected_button);
|
||
if (selected_button == "create") {
|
||
if (create_save.avatar != "") {
|
||
$("#add_avatar_button").get(0).files = create_save.avatar;
|
||
read_avatar_load($("#add_avatar_button").get(0));
|
||
}
|
||
}
|
||
|
||
selectRightMenuWithAnimation('rm_ch_create_block');
|
||
|
||
$('#set_chat_scenario').hide();
|
||
$("#delete_button_div").css("display", "none");
|
||
$("#delete_button").css("display", "none");
|
||
$("#export_button").css("display", "none");
|
||
$("#create_button_label").css("display", "");
|
||
$("#create_button").attr("value", "Create");
|
||
$("#dupe_button").hide();
|
||
|
||
//create text poles
|
||
$("#rm_button_back").css("display", "");
|
||
$("#character_import_button").css("display", "");
|
||
$("#character_popup_text_h3").text("Create character");
|
||
$("#character_name_pole").val(create_save.name);
|
||
$("#description_textarea").val(create_save.description);
|
||
$('#character_world').val(create_save.world);
|
||
$("#creator_notes_textarea").val(create_save.creator_notes);
|
||
$("#creator_notes_spoiler").text(create_save.creator_notes);
|
||
$("#post_history_instructions_textarea").val(create_save.post_history_instructions);
|
||
$("#system_prompt_textarea").val(create_save.system_prompt);
|
||
$("#tags_textarea").val(create_save.tags);
|
||
$("#creator_textarea").val(create_save.creator);
|
||
$("#character_version_textarea").val(create_save.character_version);
|
||
$("#personality_textarea").val(create_save.personality);
|
||
$("#firstmessage_textarea").val(create_save.first_message);
|
||
$("#talkativeness_slider").val(create_save.talkativeness);
|
||
$("#scenario_pole").val(create_save.scenario);
|
||
$("#mes_example_textarea").val(create_save.mes_example.trim().length === 0 ? '<START>' : create_save.mes_example);
|
||
$('#character_json_data').val('');
|
||
$("#avatar_div").css("display", "flex");
|
||
$("#avatar_load_preview").attr("src", default_avatar);
|
||
$("#renameCharButton").css('display', 'none');
|
||
$("#name_div").removeClass('displayNone');
|
||
$("#name_div").addClass('displayBlock');
|
||
$('.open_alternate_greetings').data('chid', undefined);
|
||
$('#set_character_world').data('chid', undefined);
|
||
setWorldInfoButtonClass(undefined, !!create_save.world);
|
||
updateFavButtonState(false);
|
||
checkEmbeddedWorld();
|
||
|
||
$("#form_create").attr("actiontype", "createcharacter");
|
||
}
|
||
|
||
function select_rm_characters() {
|
||
const doFullRefresh = menu_type === 'characters';
|
||
menu_type = "characters";
|
||
selectRightMenuWithAnimation('rm_characters_block');
|
||
printCharacters(doFullRefresh);
|
||
}
|
||
|
||
/**
|
||
* Sets a prompt injection to insert custom text into any outgoing prompt. For use in UI extensions.
|
||
* @param {string} key Prompt injection id.
|
||
* @param {string} value Prompt injection value.
|
||
* @param {number} position Insertion position. 0 is after story string, 1 is in-chat with custom depth.
|
||
* @param {number} depth Insertion depth. 0 represets the last message in context. Expected values up to MAX_INJECTION_DEPTH.
|
||
*/
|
||
export function setExtensionPrompt(key, value, position, depth) {
|
||
extension_prompts[key] = { value: String(value), position: Number(position), depth: Number(depth) };
|
||
}
|
||
|
||
/**
|
||
* Adds or updates the metadata for the currently active chat.
|
||
* @param {Object} newValues An object with collection of new values to be added into the metadata.
|
||
* @param {boolean} reset Should a metadata be reset by this call.
|
||
*/
|
||
function updateChatMetadata(newValues, reset) {
|
||
chat_metadata = reset ? { ...newValues } : { ...chat_metadata, ...newValues };
|
||
}
|
||
|
||
function updateFavButtonState(state) {
|
||
fav_ch_checked = state;
|
||
$("#fav_checkbox").val(fav_ch_checked);
|
||
$("#favorite_button").toggleClass('fav_on', fav_ch_checked);
|
||
$("#favorite_button").toggleClass('fav_off', !fav_ch_checked);
|
||
}
|
||
|
||
export function setScenarioOverride() {
|
||
if (!selected_group && !this_chid) {
|
||
console.warn('setScenarioOverride() -- no selected group or character');
|
||
return;
|
||
}
|
||
|
||
const template = $('#scenario_override_template .scenario_override').clone();
|
||
const metadataValue = chat_metadata['scenario'] || '';
|
||
const isGroup = !!selected_group;
|
||
template.find('[data-group="true"]').toggle(isGroup);
|
||
template.find('[data-character="true"]').toggle(!isGroup);
|
||
template.find('.chat_scenario').text(metadataValue).on('input', onScenarioOverrideInput);
|
||
template.find('.remove_scenario_override').on('click', onScenarioOverrideRemoveClick);
|
||
callPopup(template, 'text');
|
||
}
|
||
|
||
function onScenarioOverrideInput() {
|
||
const value = $(this).val();
|
||
const metadata = { scenario: value, };
|
||
updateChatMetadata(metadata, false);
|
||
saveMetadataDebounced();
|
||
}
|
||
|
||
function onScenarioOverrideRemoveClick() {
|
||
$(this).closest('.scenario_override').find('.chat_scenario').val('').trigger('input');
|
||
}
|
||
|
||
function callPopup(text, type, inputValue = '', { okButton, rows, wide, large } = {}) {
|
||
if (type) {
|
||
popup_type = type;
|
||
}
|
||
|
||
if (wide) {
|
||
$("#dialogue_popup").addClass("wide_dialogue_popup");
|
||
}
|
||
|
||
if (large) {
|
||
$("#dialogue_popup").addClass("large_dialogue_popup");
|
||
}
|
||
|
||
$("#dialogue_popup_cancel").css("display", "inline-block");
|
||
switch (popup_type) {
|
||
case "avatarToCrop":
|
||
$("#dialogue_popup_ok").text(okButton ?? "Accept");
|
||
break;
|
||
case "text":
|
||
case "alternate_greeting":
|
||
case "char_not_selected":
|
||
$("#dialogue_popup_ok").text(okButton ?? "Ok");
|
||
$("#dialogue_popup_cancel").css("display", "none");
|
||
break;
|
||
case "delete_extension":
|
||
$("#dialogue_popup_ok").text(okButton ?? "Ok");
|
||
break;
|
||
case "new_chat":
|
||
case "confirm":
|
||
$("#dialogue_popup_ok").text(okButton ?? "Yes");
|
||
break;
|
||
case "del_group":
|
||
case "rename_chat":
|
||
case "del_chat":
|
||
default:
|
||
$("#dialogue_popup_ok").text(okButton ?? "Delete");
|
||
}
|
||
|
||
$("#dialogue_popup_input").val(inputValue);
|
||
$("#dialogue_popup_input").attr("rows", rows ?? 1);
|
||
|
||
if (popup_type == 'input') {
|
||
$("#dialogue_popup_input").css("display", "block");
|
||
$("#dialogue_popup_ok").text(okButton ?? "Save");
|
||
}
|
||
else {
|
||
$("#dialogue_popup_input").css("display", "none");
|
||
}
|
||
|
||
$("#dialogue_popup_text").empty().append(text);
|
||
$("#shadow_popup").css("display", "block");
|
||
if (popup_type == 'input') {
|
||
$("#dialogue_popup_input").focus();
|
||
}
|
||
if (popup_type == 'avatarToCrop') {
|
||
// unset existing data
|
||
crop_data = undefined;
|
||
|
||
$('#avatarToCrop').cropper({
|
||
aspectRatio: 2 / 3,
|
||
autoCropArea: 1,
|
||
viewMode: 2,
|
||
rotatable: false,
|
||
crop: function (event) {
|
||
crop_data = event.detail;
|
||
crop_data.want_resize = !power_user.never_resize_avatars
|
||
}
|
||
});
|
||
}
|
||
$("#shadow_popup").transition({
|
||
opacity: 1,
|
||
duration: 200,
|
||
easing: animation_easing,
|
||
});
|
||
|
||
return new Promise((resolve) => {
|
||
dialogueResolve = resolve;
|
||
});
|
||
}
|
||
|
||
function read_bg_load(input) {
|
||
if (input.files && input.files[0]) {
|
||
var reader = new FileReader();
|
||
|
||
reader.onload = function (e) {
|
||
$("#bg_load_preview")
|
||
.attr("src", e.target.result)
|
||
.width(103)
|
||
.height(83);
|
||
|
||
var formData = new FormData($("#form_bg_download").get(0));
|
||
|
||
//console.log(formData);
|
||
jQuery.ajax({
|
||
type: "POST",
|
||
url: "/downloadbackground",
|
||
data: formData,
|
||
beforeSend: function () {
|
||
|
||
},
|
||
cache: false,
|
||
contentType: false,
|
||
processData: false,
|
||
success: function (html) {
|
||
setBackground(html);
|
||
$("#bg1").css(
|
||
"background-image",
|
||
`url("${e.target.result}")`
|
||
);
|
||
$("#form_bg_download").after(getBackgroundFromTemplate(html));
|
||
},
|
||
error: function (jqXHR, exception) {
|
||
console.log(exception);
|
||
console.log(jqXHR);
|
||
},
|
||
});
|
||
};
|
||
|
||
reader.readAsDataURL(input.files[0]);
|
||
}
|
||
}
|
||
|
||
function showSwipeButtons() {
|
||
if (chat.length === 0) {
|
||
return;
|
||
}
|
||
|
||
if (
|
||
chat[chat.length - 1].is_system ||
|
||
!swipes ||
|
||
$('.mes:last').attr('mesid') < 0 ||
|
||
chat[chat.length - 1].is_user ||
|
||
chat[chat.length - 1].extra?.image ||
|
||
count_view_mes < 1 ||
|
||
(selected_group && is_group_generating)
|
||
) { return; }
|
||
|
||
// swipe_id should be set if alternate greetings are added
|
||
if (chat.length == 1 && chat[0].swipe_id === undefined) {
|
||
return;
|
||
}
|
||
|
||
//had to add this to make the swipe counter work
|
||
//(copied from the onclick functions for swipe buttons..
|
||
//don't know why the array isn't set for non-swipe messsages in Generate or addOneMessage..)
|
||
|
||
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
|
||
}
|
||
|
||
const currentMessage = $("#chat").children().filter(`[mesid="${count_view_mes - 1}"]`);
|
||
const swipeId = chat[chat.length - 1].swipe_id;
|
||
var swipesCounterHTML = (`${(swipeId + 1)}/${(chat[chat.length - 1].swipes.length)}`);
|
||
|
||
if (swipeId !== undefined && chat[chat.length - 1].swipes.length > 1) {
|
||
currentMessage.children('.swipe_left').css('display', 'flex');
|
||
}
|
||
//only show right when generate is off, or when next right swipe would not make a generate happen
|
||
if (is_send_press === false || chat[chat.length - 1].swipes.length >= swipeId) {
|
||
currentMessage.children('.swipe_right').css('display', 'flex');
|
||
currentMessage.children('.swipe_right').css('opacity', '0.3');
|
||
}
|
||
//console.log((chat[chat.length - 1]));
|
||
if ((chat[chat.length - 1].swipes.length - swipeId) === 1) {
|
||
//console.log('highlighting R swipe');
|
||
currentMessage.children('.swipe_right').css('opacity', '0.7');
|
||
}
|
||
//console.log(swipesCounterHTML);
|
||
|
||
$(".swipes-counter").html(swipesCounterHTML);
|
||
|
||
//console.log(swipeId);
|
||
//console.log(chat[chat.length - 1].swipes.length);
|
||
}
|
||
|
||
function hideSwipeButtons() {
|
||
//console.log('hideswipebuttons entered');
|
||
$("#chat").children().filter(`[mesid="${count_view_mes - 1}"]`).children('.swipe_right').css('display', 'none');
|
||
$("#chat").children().filter(`[mesid="${count_view_mes - 1}"]`).children('.swipe_left').css('display', 'none');
|
||
}
|
||
|
||
export async function saveMetadata() {
|
||
if (selected_group) {
|
||
await editGroup(selected_group, true, false);
|
||
}
|
||
else {
|
||
await saveChatConditional();
|
||
}
|
||
}
|
||
|
||
export async function saveChatConditional() {
|
||
try {
|
||
await waitUntilCondition(() => !isChatSaving, durationSaveEdit, 100);
|
||
} catch {
|
||
console.warn('Timeout waiting for chat to save');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
isChatSaving = true;
|
||
|
||
if (selected_group) {
|
||
await saveGroupChat(selected_group, true);
|
||
}
|
||
else {
|
||
await saveChat();
|
||
}
|
||
|
||
// Save token cache to IndexedDB storage
|
||
saveTokenCache();
|
||
} catch (error) {
|
||
console.error('Error saving chat', error);
|
||
} finally {
|
||
isChatSaving = false;
|
||
}
|
||
}
|
||
|
||
async function importCharacterChat(formData) {
|
||
await jQuery.ajax({
|
||
type: "POST",
|
||
url: "/importchat",
|
||
data: formData,
|
||
beforeSend: function () {
|
||
},
|
||
cache: false,
|
||
contentType: false,
|
||
processData: false,
|
||
success: async function (data) {
|
||
if (data.res) {
|
||
await displayPastChats();
|
||
}
|
||
},
|
||
error: function () {
|
||
$("#create_button").removeAttr("disabled");
|
||
},
|
||
});
|
||
}
|
||
|
||
function updateViewMessageIds() {
|
||
const minId = getFirstDisplayedMessageId();
|
||
|
||
$('#chat').find(".mes").each(function (index, element) {
|
||
$(element).attr("mesid", minId + index);
|
||
$(element).find('.mesIDDisplay').text(`#${minId + index}`);
|
||
});
|
||
|
||
$('#chat .mes').removeClass('last_mes');
|
||
$('#chat .mes').last().addClass('last_mes');
|
||
|
||
updateEditArrowClasses();
|
||
}
|
||
|
||
function getFirstDisplayedMessageId() {
|
||
const allIds = Array.from(document.querySelectorAll('#chat .mes')).map(el => Number(el.getAttribute('mesid'))).filter(x => !isNaN(x));
|
||
const minId = Math.min(...allIds);
|
||
return minId;
|
||
}
|
||
|
||
function updateEditArrowClasses() {
|
||
$("#chat .mes .mes_edit_up").removeClass("disabled");
|
||
$("#chat .mes .mes_edit_down").removeClass("disabled");
|
||
|
||
if (this_edit_mes_id !== undefined) {
|
||
const down = $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_down`);
|
||
const up = $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_up`);
|
||
const lastId = Number($("#chat .mes").last().attr("mesid"));
|
||
const firstId = Number($("#chat .mes").first().attr("mesid"));
|
||
|
||
if (lastId == Number(this_edit_mes_id)) {
|
||
down.addClass("disabled");
|
||
}
|
||
|
||
if (firstId == Number(this_edit_mes_id)) {
|
||
up.addClass("disabled");
|
||
}
|
||
}
|
||
}
|
||
|
||
function closeMessageEditor() {
|
||
if (this_edit_mes_id) {
|
||
$(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).click();
|
||
}
|
||
}
|
||
|
||
function setGenerationProgress(progress) {
|
||
if (!progress) {
|
||
$('#send_textarea').css({ 'background': '', 'transition': '' });
|
||
}
|
||
else {
|
||
$('#send_textarea').css({
|
||
'background': `linear-gradient(90deg, #008000d6 ${progress}%, transparent ${progress}%)`,
|
||
'transition': '0.25s ease-in-out'
|
||
});
|
||
}
|
||
}
|
||
|
||
function isHordeGenerationNotAllowed() {
|
||
if (main_api == "koboldhorde" && preset_settings == "gui") {
|
||
toastr.error('GUI Settings preset is not supported for Horde. Please select another preset.');
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
export function cancelTtsPlay() {
|
||
if ('speechSynthesis' in window) {
|
||
speechSynthesis.cancel();
|
||
}
|
||
}
|
||
|
||
async function deleteMessageImage() {
|
||
const value = await callPopup("<h3>Delete image from message?<br>This action can't be undone.</h3>", 'confirm');
|
||
|
||
if (!value) {
|
||
return;
|
||
}
|
||
|
||
const mesBlock = $(this).closest('.mes');
|
||
const mesId = mesBlock.attr('mesid');
|
||
const message = chat[mesId];
|
||
delete message.extra.image;
|
||
delete message.extra.inline_image;
|
||
mesBlock.find('.mes_img_container').removeClass('img_extra');
|
||
mesBlock.find('.mes_img').attr('src', '');
|
||
await saveChatConditional();
|
||
}
|
||
|
||
function enlargeMessageImage() {
|
||
const mesBlock = $(this).closest('.mes');
|
||
const mesId = mesBlock.attr('mesid');
|
||
const message = chat[mesId];
|
||
const imgSrc = message?.extra?.image;
|
||
|
||
if (!imgSrc) {
|
||
return;
|
||
}
|
||
|
||
const img = document.createElement('img');
|
||
img.classList.add('img_enlarged');
|
||
img.src = imgSrc;
|
||
callPopup(img.outerHTML, 'text', '', { wide: true, large: true });
|
||
}
|
||
|
||
function updateAlternateGreetingsHintVisibility(root) {
|
||
const numberOfGreetings = root.find('.alternate_greetings_list .alternate_greeting').length;
|
||
$(root).find('.alternate_grettings_hint').toggle(numberOfGreetings == 0);
|
||
}
|
||
|
||
function openCharacterWorldPopup() {
|
||
const chid = $('#set_character_world').data('chid');
|
||
|
||
if (menu_type != 'create' && chid == undefined) {
|
||
toastr.error('Does not have an Id for this character in world select menu.');
|
||
return;
|
||
}
|
||
|
||
async function onSelectCharacterWorld() {
|
||
const value = $('.character_world_info_selector').find('option:selected').val();
|
||
const worldIndex = value !== '' ? Number(value) : NaN;
|
||
const name = !isNaN(worldIndex) ? world_names[worldIndex] : '';
|
||
|
||
const previousValue = $('#character_world').val();
|
||
$('#character_world').val(name);
|
||
|
||
console.debug('Character world selected:', name);
|
||
|
||
if (menu_type == 'create') {
|
||
create_save.world = name;
|
||
} else {
|
||
if (previousValue && !name) {
|
||
try {
|
||
// Dirty hack to remove embedded lorebook from character JSON data.
|
||
const data = JSON.parse($('#character_json_data').val());
|
||
|
||
if (data?.data?.character_book) {
|
||
data.data.character_book = undefined;
|
||
}
|
||
|
||
$('#character_json_data').val(JSON.stringify(data));
|
||
toastr.info('Embedded lorebook will be removed from this character.');
|
||
} catch {
|
||
console.error('Failed to parse character JSON data.');
|
||
}
|
||
}
|
||
|
||
await createOrEditCharacter();
|
||
}
|
||
|
||
setWorldInfoButtonClass(undefined, !!value);
|
||
}
|
||
|
||
function onExtraWorldInfoChanged() {
|
||
const selectedWorlds = $('.character_extra_world_info_selector').val();
|
||
let charLore = world_info.charLore ?? [];
|
||
|
||
// TODO: Maybe make this utility function not use the window context?
|
||
const fileName = getCharaFilename(chid);
|
||
const tempExtraBooks = selectedWorlds.map((index) => world_names[index]).filter((e) => e !== undefined);
|
||
|
||
const existingCharIndex = charLore.findIndex((e) => e.name === fileName);
|
||
if (existingCharIndex === -1) {
|
||
const newCharLoreEntry = {
|
||
name: fileName,
|
||
extraBooks: tempExtraBooks
|
||
}
|
||
|
||
charLore.push(newCharLoreEntry);
|
||
} else if (tempExtraBooks.length === 0) {
|
||
charLore.splice(existingCharIndex, 1);
|
||
} else {
|
||
charLore[existingCharIndex].extraBooks = tempExtraBooks;
|
||
}
|
||
|
||
Object.assign(world_info, { charLore: charLore });
|
||
saveSettingsDebounced();
|
||
}
|
||
|
||
const template = $('#character_world_template .character_world').clone();
|
||
const select = template.find('.character_world_info_selector');
|
||
const extraSelect = template.find('.character_extra_world_info_selector');
|
||
const name = (menu_type == 'create' ? create_save.name : characters[chid]?.data?.name) || 'Nameless';
|
||
const worldId = (menu_type == 'create' ? create_save.world : characters[chid]?.data?.extensions?.world) || '';
|
||
template.find('.character_name').text(name);
|
||
|
||
// Not needed on mobile
|
||
const deviceInfo = getDeviceInfo();
|
||
if (deviceInfo && deviceInfo.device.type === 'desktop') {
|
||
$(extraSelect).select2({
|
||
width: '100%',
|
||
placeholder: 'No auxillary Lorebooks set. Click here to select.',
|
||
allowClear: true,
|
||
closeOnSelect: false,
|
||
});
|
||
}
|
||
|
||
// Apped to base dropdown
|
||
world_names.forEach((item, i) => {
|
||
const option = document.createElement('option');
|
||
option.value = i;
|
||
option.innerText = item;
|
||
option.selected = item === worldId;
|
||
select.append(option);
|
||
});
|
||
|
||
// Append to extras dropdown
|
||
if (world_names.length > 0) {
|
||
extraSelect.empty();
|
||
}
|
||
world_names.forEach((item, i) => {
|
||
const option = document.createElement('option');
|
||
option.value = i;
|
||
option.innerText = item;
|
||
|
||
const existingCharLore = world_info.charLore?.find((e) => e.name === getCharaFilename());
|
||
if (existingCharLore) {
|
||
option.selected = existingCharLore.extraBooks.includes(item);
|
||
} else {
|
||
option.selected = false;
|
||
}
|
||
extraSelect.append(option);
|
||
});
|
||
|
||
select.on('change', onSelectCharacterWorld);
|
||
extraSelect.on('mousedown change', async function (e) {
|
||
// If there's no world names, don't do anything
|
||
if (world_names.length === 0) {
|
||
e.preventDefault();
|
||
return;
|
||
}
|
||
|
||
onExtraWorldInfoChanged();
|
||
});
|
||
|
||
callPopup(template, 'text');
|
||
}
|
||
|
||
function openAlternateGreetings() {
|
||
const chid = $('.open_alternate_greetings').data('chid');
|
||
|
||
if (menu_type != 'create' && chid === undefined) {
|
||
toastr.error('Does not have an Id for this character in editor menu.');
|
||
return;
|
||
} else {
|
||
// If the character does not have alternate greetings, create an empty array
|
||
if (chid && Array.isArray(characters[chid].data.alternate_greetings) == false) {
|
||
characters[chid].data.alternate_greetings = [];
|
||
}
|
||
}
|
||
|
||
const template = $('#alternate_greetings_template .alternate_grettings').clone();
|
||
const getArray = () => menu_type == 'create' ? create_save.alternate_greetings : characters[chid].data.alternate_greetings;
|
||
|
||
for (let index = 0; index < getArray().length; index++) {
|
||
addAlternateGreeting(template, getArray()[index], index, getArray);
|
||
}
|
||
|
||
template.find('.add_alternate_greeting').on('click', function () {
|
||
const array = getArray();
|
||
const index = array.length;
|
||
array.push(default_ch_mes);
|
||
addAlternateGreeting(template, default_ch_mes, index, getArray);
|
||
updateAlternateGreetingsHintVisibility(template);
|
||
});
|
||
|
||
updateAlternateGreetingsHintVisibility(template);
|
||
callPopup(template, 'alternate_greeting');
|
||
}
|
||
|
||
function addAlternateGreeting(template, greeting, index, getArray) {
|
||
const greetingBlock = $('#alternate_greeting_form_template .alternate_greeting').clone();
|
||
greetingBlock.find('.alternate_greeting_text').on('input', async function () {
|
||
const value = $(this).val();
|
||
const array = getArray();
|
||
array[index] = value;
|
||
}).val(greeting);
|
||
greetingBlock.find('.greeting_index').text(index + 1);
|
||
greetingBlock.find('.delete_alternate_greeting').on('click', async function () {
|
||
if (confirm('Are you sure you want to delete this alternate greeting?')) {
|
||
const array = getArray();
|
||
array.splice(index, 1);
|
||
// We need to reopen the popup to update the index numbers
|
||
openAlternateGreetings();
|
||
}
|
||
});
|
||
template.find('.alternate_greetings_list').append(greetingBlock);
|
||
}
|
||
|
||
async function createOrEditCharacter(e) {
|
||
$("#rm_info_avatar").html("");
|
||
let save_name = create_save.name;
|
||
var formData = new FormData($("#form_create").get(0));
|
||
formData.set('fav', fav_ch_checked);
|
||
if ($("#form_create").attr("actiontype") == "createcharacter") {
|
||
if ($("#character_name_pole").val().length > 0) {
|
||
//if the character name text area isn't empty (only posible when creating a new character)
|
||
let url = "/createcharacter";
|
||
|
||
if (crop_data != undefined) {
|
||
url += `?crop=${encodeURIComponent(JSON.stringify(crop_data))}`;
|
||
}
|
||
|
||
formData.delete('alternate_greetings');
|
||
for (const value of create_save.alternate_greetings) {
|
||
formData.append('alternate_greetings', value);
|
||
}
|
||
|
||
await jQuery.ajax({
|
||
type: "POST",
|
||
url: url,
|
||
data: formData,
|
||
beforeSend: function () {
|
||
$("#create_button").attr("disabled", true);
|
||
$("#create_button").attr("value", "⏳");
|
||
},
|
||
cache: false,
|
||
contentType: false,
|
||
processData: false,
|
||
success: async function (html) {
|
||
$("#character_cross").trigger('click'); //closes the advanced character editing popup
|
||
const fields = [
|
||
{ id: '#character_name_pole', callback: value => create_save.name = value },
|
||
{ id: '#description_textarea', callback: value => create_save.description = value },
|
||
{ id: '#creator_notes_textarea', callback: value => create_save.creator_notes = value },
|
||
{ id: '#character_version_textarea', callback: value => create_save.character_version = value },
|
||
{ id: '#post_history_instructions_textarea', callback: value => create_save.post_history_instructions = value },
|
||
{ id: '#system_prompt_textarea', callback: value => create_save.system_prompt = value },
|
||
{ id: '#tags_textarea', callback: value => create_save.tags = value },
|
||
{ id: '#creator_textarea', callback: value => create_save.creator = value },
|
||
{ id: '#personality_textarea', callback: value => create_save.personality = value },
|
||
{ id: '#firstmessage_textarea', callback: value => create_save.first_message = value },
|
||
{ id: '#talkativeness_slider', callback: value => create_save.talkativeness = value, defaultValue: talkativeness_default },
|
||
{ id: '#scenario_pole', callback: value => create_save.scenario = value },
|
||
{ id: '#mes_example_textarea', callback: value => create_save.mes_example = value },
|
||
{ id: '#character_json_data', callback: () => { } },
|
||
{ id: '#alternate_greetings_template', callback: value => create_save.alternate_greetings = value, defaultValue: [] },
|
||
{ id: '#character_world', callback: value => create_save.world = value },
|
||
];
|
||
|
||
fields.forEach(field => {
|
||
const fieldValue = field.defaultValue !== undefined ? field.defaultValue : '';
|
||
$(field.id).val(fieldValue);
|
||
field.callback && field.callback(fieldValue);
|
||
});
|
||
|
||
$("#character_popup_text_h3").text("Create character");
|
||
|
||
create_save.avatar = "";
|
||
|
||
$("#create_button").removeAttr("disabled");
|
||
$("#add_avatar_button").replaceWith(
|
||
$("#add_avatar_button").val("").clone(true)
|
||
);
|
||
|
||
$("#create_button").attr("value", "✅");
|
||
let oldSelectedChar = null;
|
||
if (this_chid != undefined && this_chid != "invalid-safety-id") {
|
||
oldSelectedChar = characters[this_chid].avatar;
|
||
}
|
||
|
||
console.log(`new avatar id: ${html}`);
|
||
createTagMapFromList("#tagList", html);
|
||
await getCharacters();
|
||
|
||
select_rm_info(`char_create`, html, oldSelectedChar);
|
||
|
||
crop_data = undefined;
|
||
},
|
||
error: function (jqXHR, exception) {
|
||
$("#create_button").removeAttr("disabled");
|
||
},
|
||
});
|
||
} else {
|
||
toastr.error('Name is required');
|
||
}
|
||
} else {
|
||
let url = '/editcharacter';
|
||
|
||
if (crop_data != undefined) {
|
||
url += `?crop=${encodeURIComponent(JSON.stringify(crop_data))}`;
|
||
}
|
||
|
||
formData.delete('alternate_greetings');
|
||
const chid = $('.open_alternate_greetings').data('chid');
|
||
if (chid && Array.isArray(characters[chid]?.data?.alternate_greetings)) {
|
||
for (const value of characters[chid].data.alternate_greetings) {
|
||
formData.append('alternate_greetings', value);
|
||
}
|
||
}
|
||
|
||
await jQuery.ajax({
|
||
type: "POST",
|
||
url: url,
|
||
data: formData,
|
||
beforeSend: function () {
|
||
$("#create_button").attr("disabled", true);
|
||
$("#create_button").attr("value", "Save");
|
||
},
|
||
cache: false,
|
||
contentType: false,
|
||
processData: false,
|
||
success: async function (html) {
|
||
$("#create_button").removeAttr("disabled");
|
||
|
||
await getOneCharacter(formData.get('avatar_url'));
|
||
favsToHotswap(); // Update fav state
|
||
|
||
$("#add_avatar_button").replaceWith(
|
||
$("#add_avatar_button").val("").clone(true)
|
||
);
|
||
$("#create_button").attr("value", "Save");
|
||
crop_data = undefined;
|
||
eventSource.emit(event_types.CHARACTER_EDITED, { detail: { id: this_chid, character: characters[this_chid] } });
|
||
|
||
if (chat.length === 1 && !selected_group) {
|
||
const firstMessage = getFirstMessage();
|
||
chat[0] = firstMessage;
|
||
|
||
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
|
||
clearChat();
|
||
await printMessages();
|
||
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
|
||
await saveChatConditional();
|
||
}
|
||
},
|
||
error: function (jqXHR, exception) {
|
||
$("#create_button").removeAttr("disabled");
|
||
console.log('Error! Either a file with the same name already existed, or the image file provided was in an invalid format. Double check that the image is not a webp.');
|
||
toastr.error('Something went wrong while saving the character, or the image file provided was in an invalid format. Double check that the image is not a webp.');
|
||
},
|
||
});
|
||
}
|
||
}
|
||
|
||
window["SillyTavern"].getContext = function () {
|
||
return {
|
||
chat: chat,
|
||
characters: characters,
|
||
groups: groups,
|
||
name1: name1,
|
||
name2: name2,
|
||
characterId: this_chid,
|
||
groupId: selected_group,
|
||
chatId: selected_group
|
||
? groups.find(x => x.id == selected_group)?.chat_id
|
||
: (this_chid && characters[this_chid] && characters[this_chid].chat),
|
||
onlineStatus: online_status,
|
||
maxContext: Number(max_context),
|
||
chatMetadata: chat_metadata,
|
||
streamingProcessor,
|
||
eventSource: eventSource,
|
||
event_types: event_types,
|
||
addOneMessage: addOneMessage,
|
||
generate: Generate,
|
||
getTokenCount: getTokenCount,
|
||
extensionPrompts: extension_prompts,
|
||
setExtensionPrompt: setExtensionPrompt,
|
||
updateChatMetadata: updateChatMetadata,
|
||
saveChat: saveChatConditional,
|
||
saveMetadata: saveMetadata,
|
||
sendSystemMessage: sendSystemMessage,
|
||
activateSendButtons,
|
||
deactivateSendButtons,
|
||
saveReply,
|
||
registerSlashCommand: registerSlashCommand,
|
||
registerHelper: registerExtensionHelper,
|
||
registedDebugFunction: registerDebugFunction,
|
||
};
|
||
};
|
||
|
||
function swipe_left() { // when we swipe left..but no generation.
|
||
if (chat.length - 1 === Number(this_edit_mes_id)) {
|
||
closeMessageEditor();
|
||
}
|
||
|
||
if (isStreamingEnabled() && streamingProcessor) {
|
||
streamingProcessor.isStopped = true;
|
||
}
|
||
|
||
const swipe_duration = 120;
|
||
const swipe_range = '700px';
|
||
chat[chat.length - 1]['swipe_id']--;
|
||
|
||
if (chat[chat.length - 1]['swipe_id'] < 0) {
|
||
chat[chat.length - 1]['swipe_id'] = chat[chat.length - 1]['swipes'].length - 1;
|
||
}
|
||
|
||
if (chat[chat.length - 1]['swipe_id'] >= 0) {
|
||
/*$(this).parent().children('swipe_right').css('display', 'flex');
|
||
if (chat[chat.length - 1]['swipe_id'] === 0) {
|
||
$(this).css('display', 'none');
|
||
}*/ // Just in case
|
||
if (!Array.isArray(chat[chat.length - 1]['swipe_info'])) {
|
||
chat[chat.length - 1]['swipe_info'] = [];
|
||
}
|
||
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;
|
||
this_mes_div.css('height', this_mes_div_height);
|
||
const this_mes_block_height = this_mes_block[0].scrollHeight;
|
||
chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']];
|
||
chat[chat.length - 1]['send_date'] = chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.send_date || chat[chat.length - 1].send_date; //load the last mes box with the latest generation
|
||
chat[chat.length - 1]['extra'] = JSON.parse(JSON.stringify(chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.extra || chat[chat.length - 1].extra));
|
||
|
||
if (chat[chat.length - 1].extra) {
|
||
// if message has memory attached - remove it to allow regen
|
||
if (chat[chat.length - 1].extra.memory) {
|
||
delete chat[chat.length - 1].extra.memory;
|
||
}
|
||
// ditto for display text
|
||
if (chat[chat.length - 1].extra.display_text) {
|
||
delete chat[chat.length - 1].extra.display_text;
|
||
}
|
||
}
|
||
$(this).parent().children('.mes_block').transition({
|
||
x: swipe_range,
|
||
duration: swipe_duration,
|
||
easing: animation_easing,
|
||
queue: false,
|
||
complete: function () {
|
||
const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop("scrollHeight") - $('#chat').outerHeight()) - 10);
|
||
//console.log('on left swipe click calling addOneMessage');
|
||
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
|
||
|
||
if (power_user.message_token_count_enabled) {
|
||
if (!chat[chat.length - 1].extra) {
|
||
chat[chat.length - 1].extra = {};
|
||
}
|
||
|
||
const swipeMessage = $("#chat").find(`[mesid="${count_view_mes - 1}"]`);
|
||
const tokenCount = getTokenCount(chat[chat.length - 1].mes, 0);
|
||
chat[chat.length - 1]['extra']['token_count'] = tokenCount;
|
||
swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);
|
||
}
|
||
|
||
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).parent().children('.mes_block').transition({
|
||
x: '-' + swipe_range,
|
||
duration: 0,
|
||
easing: animation_easing,
|
||
queue: false,
|
||
complete: function () {
|
||
$(this).parent().children('.mes_block').transition({
|
||
x: '0px',
|
||
duration: swipe_duration,
|
||
easing: animation_easing,
|
||
queue: false,
|
||
complete: async function () {
|
||
await eventSource.emit(event_types.MESSAGE_SWIPED, (chat.length - 1));
|
||
saveChatDebounced();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
$(this).parent().children('.avatar').transition({
|
||
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 () {
|
||
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
if (chat[chat.length - 1]['swipe_id'] < 0) {
|
||
chat[chat.length - 1]['swipe_id'] = 0;
|
||
}
|
||
}
|
||
|
||
async function branchChat(mesID) {
|
||
let name = await createBranch(mesID);
|
||
await openCharacterChat(name);
|
||
}
|
||
|
||
// 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]['swipe_info'] = [];
|
||
chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat
|
||
chat[chat.length - 1]['swipe_info'][0] = { 'send_date': chat[chat.length - 1]['send_date'], 'gen_started': chat[chat.length - 1]['gen_started'], 'gen_finished': chat[chat.length - 1]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[chat.length - 1]['extra'])) };
|
||
//assign swipe info array with last message from chat
|
||
}
|
||
if (chat.length === 1 && chat[0]['swipe_id'] !== undefined && chat[0]['swipe_id'] === chat[0]['swipes'].length - 1) { // if swipe_right is called on the last alternate greeting, loop back around
|
||
chat[0]['swipe_id'] = 0;
|
||
} else {
|
||
chat[chat.length - 1]['swipe_id']++; // make new slot in array
|
||
}
|
||
if (chat[chat.length - 1].extra) {
|
||
// if message has memory attached - remove it to allow regen
|
||
if (chat[chat.length - 1].extra.memory) {
|
||
delete chat[chat.length - 1].extra.memory;
|
||
}
|
||
// ditto for display text
|
||
if (chat[chat.length - 1].extra.display_text) {
|
||
delete chat[chat.length - 1].extra.display_text;
|
||
}
|
||
}
|
||
if (!Array.isArray(chat[chat.length - 1]['swipe_info'])) {
|
||
chat[chat.length - 1]['swipe_info'] = [];
|
||
}
|
||
//console.log(chat[chat.length-1]['swipes']);
|
||
if (parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length && chat.length !== 1) { //if swipe id of last message is the same as the length of the 'swipes' array and not the greeting
|
||
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
|
||
chat[chat.length - 1]['send_date'] = chat[chat.length - 1]?.swipe_info[chat[chat.length - 1]['swipe_id']]?.send_date || chat[chat.length - 1]['send_date']; //update send date
|
||
chat[chat.length - 1]['extra'] = JSON.parse(JSON.stringify(chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.extra || chat[chat.length - 1].extra || []));
|
||
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);
|
||
const swipeMessage = $("#chat").find('[mesid="' + (count_view_mes - 1) + '"]');
|
||
if (run_generate && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) {
|
||
//shows "..." while generating
|
||
swipeMessage.find('.mes_text').html('...');
|
||
// resets the timer
|
||
swipeMessage.find('.mes_timer').html('');
|
||
swipeMessage.find('.tokenCounterDisplay').text('');
|
||
} else {
|
||
//console.log('showing previously generated swipe candidate, or "..."');
|
||
//console.log('onclick right swipe calling addOneMessage');
|
||
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
|
||
|
||
if (power_user.message_token_count_enabled) {
|
||
if (!chat[chat.length - 1].extra) {
|
||
chat[chat.length - 1].extra = {};
|
||
}
|
||
|
||
const tokenCount = getTokenCount(chat[chat.length - 1].mes, 0);
|
||
chat[chat.length - 1]['extra']['token_count'] = tokenCount;
|
||
swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);
|
||
}
|
||
}
|
||
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: async function () {
|
||
await eventSource.emit(event_types.MESSAGE_SWIPED, (chat.length - 1));
|
||
if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) {
|
||
console.debug('caught here 2');
|
||
is_send_press = true;
|
||
$('.mes_buttons:last').hide();
|
||
await Generate('swipe');
|
||
} else {
|
||
if (parseInt(chat[chat.length - 1]['swipe_id']) !== chat[chat.length - 1]['swipes'].length) {
|
||
saveChatDebounced();
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
});
|
||
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 () {
|
||
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
|
||
|
||
function displayOverrideWarnings() {
|
||
if (!this_chid || !selected_group) {
|
||
$('.prompt_overridden').hide();
|
||
$('.jailbreak_overridden').hide();
|
||
return;
|
||
}
|
||
|
||
$('.prompt_overridden').toggle(!!(characters[this_chid]?.data?.system_prompt));
|
||
$('.jailbreak_overridden').toggle(!!(characters[this_chid]?.data?.post_history_instructions));
|
||
}
|
||
|
||
function connectAPISlash(_, text) {
|
||
if (!text) return;
|
||
|
||
const apiMap = {
|
||
'kobold': {
|
||
button: '#api_button',
|
||
},
|
||
'horde': {
|
||
selected: 'koboldhorde',
|
||
},
|
||
'novel': {
|
||
button: '#api_button_novel',
|
||
},
|
||
'ooba': {
|
||
button: '#api_button_textgenerationwebui',
|
||
},
|
||
'oai': {
|
||
selected: 'openai',
|
||
source: 'openai',
|
||
button: '#api_button_openai',
|
||
},
|
||
'claude': {
|
||
selected: 'openai',
|
||
source: 'claude',
|
||
button: '#api_button_openai',
|
||
},
|
||
'windowai': {
|
||
selected: 'openai',
|
||
source: 'windowai',
|
||
button: '#api_button_openai',
|
||
},
|
||
'openrouter': {
|
||
selected: 'openai',
|
||
source: 'openrouter',
|
||
button: '#api_button_openai',
|
||
},
|
||
'ai21': {
|
||
selected: 'openai',
|
||
source: 'ai21',
|
||
button: '#api_button_openai',
|
||
},
|
||
'palm': {
|
||
selected: 'openai',
|
||
source: 'palm',
|
||
button: '#api_button_openai',
|
||
},
|
||
};
|
||
|
||
const apiConfig = apiMap[text];
|
||
if (!apiConfig) {
|
||
toastr.error(`Error: ${text} is not a valid API`);
|
||
return;
|
||
}
|
||
|
||
$(`#main_api option[value='${apiConfig.selected || text}']`).prop("selected", true);
|
||
$("#main_api").trigger('change');
|
||
|
||
if (apiConfig.source) {
|
||
$(`#chat_completion_source option[value='${apiConfig.source}']`).prop("selected", true);
|
||
$("#chat_completion_source").trigger('change');
|
||
}
|
||
|
||
if (apiConfig.button) {
|
||
$(apiConfig.button).trigger('click');
|
||
}
|
||
|
||
toastr.info(`API set to ${text}, trying to connect..`);
|
||
}
|
||
|
||
export function processDroppedFiles(files) {
|
||
const allowedMimeTypes = [
|
||
'application/json',
|
||
'image/png',
|
||
];
|
||
|
||
for (const file of files) {
|
||
if (allowedMimeTypes.includes(file.type)) {
|
||
importCharacter(file);
|
||
} else {
|
||
toastr.warning('Unsupported file type: ' + file.name);
|
||
}
|
||
}
|
||
}
|
||
|
||
function importCharacter(file) {
|
||
const ext = file.name.match(/\.(\w+)$/);
|
||
if (
|
||
!ext ||
|
||
(ext[1].toLowerCase() != "json" && ext[1].toLowerCase() != "png")
|
||
) {
|
||
return;
|
||
}
|
||
|
||
const format = ext[1].toLowerCase();
|
||
$("#character_import_file_type").val(format);
|
||
const formData = new FormData();
|
||
formData.append('avatar', file);
|
||
formData.append('file_type', format);
|
||
|
||
jQuery.ajax({
|
||
type: "POST",
|
||
url: "/importcharacter",
|
||
data: formData,
|
||
async: false,
|
||
beforeSend: function () {
|
||
},
|
||
cache: false,
|
||
contentType: false,
|
||
processData: false,
|
||
success: async function (data) {
|
||
if (data.error) {
|
||
toastr.error('The file is likely invalid or corrupted.', 'Could not import character');
|
||
return;
|
||
}
|
||
|
||
if (data.file_name !== undefined) {
|
||
$('#character_search_bar').val('').trigger('input');
|
||
|
||
let oldSelectedChar = null;
|
||
if (this_chid != undefined && this_chid != "invalid-safety-id") {
|
||
oldSelectedChar = characters[this_chid].avatar;
|
||
}
|
||
|
||
await getCharacters();
|
||
select_rm_info(`char_import`, data.file_name, oldSelectedChar);
|
||
if (power_user.import_card_tags) {
|
||
let currentContext = getContext();
|
||
let avatarFileName = `${data.file_name}.png`;
|
||
let importedCharacter = currentContext.characters.find(character => character.avatar === avatarFileName);
|
||
await importTags(importedCharacter);
|
||
}
|
||
}
|
||
},
|
||
error: function (jqXHR, exception) {
|
||
$("#create_button").removeAttr("disabled");
|
||
},
|
||
});
|
||
}
|
||
|
||
async function importFromURL(items, files) {
|
||
for (const item of items) {
|
||
if (item.type === 'text/uri-list') {
|
||
const uriList = await new Promise((resolve) => {
|
||
item.getAsString((uriList) => { resolve(uriList); });
|
||
});
|
||
const uris = uriList.split('\n').filter(uri => uri.trim() !== '');
|
||
try {
|
||
for (const uri of uris) {
|
||
const request = await fetch(uri);
|
||
const data = await request.blob();
|
||
const fileName = request.headers.get('Content-Disposition')?.split('filename=')[1]?.replace(/"/g, '') || uri.split('/').pop() || 'file.png';
|
||
const file = new File([data], fileName, { type: data.type });
|
||
files.push(file);
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to import from URL', error);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
async function doImpersonate() {
|
||
$('#send_textarea').val('');
|
||
$("#option_impersonate").trigger('click', { fromSlashCommand: true })
|
||
}
|
||
|
||
async function doDeleteChat() {
|
||
$("#option_select_chat").trigger('click', { fromSlashCommand: true })
|
||
await delay(100)
|
||
let currentChatDeleteButton = $(".select_chat_block[highlight='true']").parent().find('.PastChat_cross')
|
||
$(currentChatDeleteButton).trigger('click', { fromSlashCommand: true })
|
||
await delay(1)
|
||
$("#dialogue_popup_ok").trigger('click')
|
||
//200 delay needed let the past chat view reshow first
|
||
await delay(200)
|
||
$("#select_chat_cross").trigger('click')
|
||
}
|
||
|
||
const isPwaMode = window.navigator.standalone;
|
||
if (isPwaMode) { $("body").addClass('PWA') }
|
||
|
||
function doCharListDisplaySwitch() {
|
||
console.debug('toggling body charListGrid state')
|
||
$("body").toggleClass('charListGrid')
|
||
power_user.charListGrid = $("body").hasClass("charListGrid") ? true : false;
|
||
saveSettingsDebounced();
|
||
}
|
||
|
||
function doCloseChat() {
|
||
$("#option_close_chat").trigger('click')
|
||
}
|
||
|
||
/**
|
||
* Function to handle the deletion of a character, given a specific popup type and character ID.
|
||
* If popup type equals "del_ch", it will proceed with deletion otherwise it will exit the function.
|
||
* It fetches the delete character route, sending necessary parameters, and in case of success,
|
||
* it proceeds to delete character from UI and saves settings.
|
||
* In case of error during the fetch request, it logs the error details.
|
||
*
|
||
* @param {string} popup_type - The type of popup currently active.
|
||
* @param {string} this_chid - The character ID to be deleted.
|
||
* @param {boolean} delete_chats - Whether to delete chats or not.
|
||
*/
|
||
export async function handleDeleteCharacter(popup_type, this_chid, delete_chats) {
|
||
if (popup_type !== "del_ch") {
|
||
return;
|
||
}
|
||
|
||
const avatar = characters[this_chid].avatar;
|
||
const name = characters[this_chid].name;
|
||
const pastChats = await getPastCharacterChats();
|
||
|
||
const msg = { avatar_url: avatar, delete_chats: delete_chats };
|
||
|
||
const response = await fetch('/deletecharacter', {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify(msg),
|
||
cache: 'no-cache',
|
||
});
|
||
|
||
if (response.ok) {
|
||
await deleteCharacter(name, avatar);
|
||
|
||
if (delete_chats) {
|
||
for (const chat of pastChats) {
|
||
const name = chat.file_name.replace('.jsonl', '');
|
||
await eventSource.emit(event_types.CHAT_DELETED, name);
|
||
}
|
||
}
|
||
} else {
|
||
console.error('Failed to delete character: ', response.status, response.statusText);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Function to delete a character from UI after character deletion API success.
|
||
* It manages necessary UI changes such as closing advanced editing popup, unsetting
|
||
* character ID, resetting characters array and chat metadata, deselecting character's tab
|
||
* panel, removing character name from navigation tabs, clearing chat, removing character's
|
||
* avatar from tag_map, fetching updated list of characters and updating the 'deleted
|
||
* character' message.
|
||
* It also ensures to save the settings after all the operations.
|
||
*
|
||
* @param {string} name - The name of the character to be deleted.
|
||
* @param {string} avatar - The avatar URL of the character to be deleted.
|
||
*/
|
||
export async function deleteCharacter(name, avatar) {
|
||
$("#character_cross").click();
|
||
this_chid = "invalid-safety-id";
|
||
characters.length = 0;
|
||
name2 = systemUserName;
|
||
chat = [...safetychat];
|
||
chat_metadata = {};
|
||
$(document.getElementById("rm_button_selected_ch")).children("h2").text("");
|
||
clearChat();
|
||
this_chid = undefined;
|
||
delete tag_map[avatar];
|
||
await getCharacters();
|
||
select_rm_info("char_delete", name);
|
||
await printMessages();
|
||
saveSettingsDebounced();
|
||
}
|
||
|
||
function doTogglePanels() {
|
||
$("#option_settings").trigger('click')
|
||
}
|
||
|
||
jQuery(async function () {
|
||
|
||
if (isMobile() === true) {
|
||
console.debug('hiding movingUI and sheldWidth toggles for mobile')
|
||
$("#sheldWidthToggleBlock").hide();
|
||
$("#movingUIModeCheckBlock").hide();
|
||
|
||
}
|
||
|
||
registerSlashCommand('dupe', DupeChar, [], "– duplicates the currently selected character", true, true);
|
||
registerSlashCommand('api', connectAPISlash, [], "(kobold, horde, novel, ooba, oai, claude, windowai, ai21, palm) – connect to an API", true, true);
|
||
registerSlashCommand('impersonate', doImpersonate, ['imp'], "- calls an impersonation response", true, true);
|
||
registerSlashCommand('delchat', doDeleteChat, [], "- deletes the current chat", true, true);
|
||
registerSlashCommand('closechat', doCloseChat, [], "- closes the current chat", true, true);
|
||
registerSlashCommand('panels', doTogglePanels, ['togglepanels'], "- toggle UI panels on/off", true, true);
|
||
|
||
setTimeout(function () {
|
||
$("#groupControlsToggle").trigger('click');
|
||
$("#groupCurrentMemberListToggle .inline-drawer-icon").trigger('click');
|
||
}, 200);
|
||
|
||
$("#chat").on('mousewheel touchstart', () => {
|
||
scrollLock = true;
|
||
});
|
||
|
||
//////////INPUT BAR FOCUS-KEEPING LOGIC/////////////
|
||
let S_TAFocused = false;
|
||
let S_TAPreviouslyFocused = false;
|
||
$('#send_textarea').on('focusin focus click', () => {
|
||
S_TAFocused = true;
|
||
S_TAPreviouslyFocused = true;
|
||
});
|
||
$('#send_textarea').on('focusout blur', () => S_TAFocused = false);
|
||
$('#options_button, #send_but, #option_regenerate, #option_continue, #mes_continue').on('click', () => {
|
||
if (S_TAPreviouslyFocused) {
|
||
$('#send_textarea').focus();
|
||
S_TAFocused = true;
|
||
}
|
||
});
|
||
$(document).click(event => {
|
||
if ($(':focus').attr('id') !== 'send_textarea') {
|
||
var validIDs = ["options_button", "send_but", "mes_continue", "send_textarea", "option_regenerate", "option_continue"];
|
||
if (!validIDs.includes($(event.target).attr('id'))) {
|
||
S_TAFocused = false;
|
||
S_TAPreviouslyFocused = false;
|
||
}
|
||
} else {
|
||
S_TAFocused = true;
|
||
S_TAPreviouslyFocused = true;
|
||
}
|
||
});
|
||
|
||
/////////////////
|
||
|
||
$('#swipes-checkbox').change(function () {
|
||
swipes = !!$('#swipes-checkbox').prop('checked');
|
||
if (swipes) {
|
||
//console.log('toggle change calling showswipebtns');
|
||
showSwipeButtons();
|
||
} else {
|
||
hideSwipeButtons();
|
||
}
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
///// SWIPE BUTTON CLICKS ///////
|
||
|
||
$(document).on('click', '.swipe_right', swipe_right);
|
||
|
||
$(document).on('click', '.swipe_left', swipe_left);
|
||
|
||
$("#character_search_bar").on("input", function () {
|
||
const searchValue = String($(this).val()).toLowerCase();
|
||
entitiesFilter.setFilterData(FILTER_TYPES.SEARCH, searchValue);
|
||
});
|
||
|
||
$("#mes_continue").on('click', function () {
|
||
$("#option_continue").trigger('click');
|
||
});
|
||
|
||
$("#send_but").on('click', function () {
|
||
if (is_send_press == false) {
|
||
is_send_press = true;
|
||
Generate();
|
||
}
|
||
});
|
||
|
||
//menu buttons setup
|
||
|
||
$("#rm_button_settings").click(function () {
|
||
selected_button = "settings";
|
||
menu_type = "settings";
|
||
selectRightMenuWithAnimation('rm_api_block');
|
||
});
|
||
$("#rm_button_characters").click(function () {
|
||
selected_button = "characters";
|
||
select_rm_characters();
|
||
});
|
||
$("#rm_button_back").click(function () {
|
||
selected_button = "characters";
|
||
select_rm_characters();
|
||
});
|
||
$("#rm_button_create").click(function () {
|
||
selected_button = "create";
|
||
select_rm_create();
|
||
});
|
||
$("#rm_button_selected_ch").click(function () {
|
||
if (selected_group) {
|
||
select_group_chats(selected_group);
|
||
} else {
|
||
selected_button = "character_edit";
|
||
select_selected_character(this_chid);
|
||
}
|
||
$("#character_search_bar").val("").trigger("input");
|
||
});
|
||
|
||
$(document).on("click", ".character_select", function () {
|
||
const id = $(this).attr("chid");
|
||
selectCharacterById(id);
|
||
});
|
||
|
||
$(document).on("input", ".edit_textarea", function () {
|
||
scroll_holder = $("#chat").scrollTop();
|
||
$(this).height(0).height(this.scrollHeight);
|
||
is_use_scroll_holder = true;
|
||
});
|
||
$("#chat").on("scroll", function () {
|
||
if (is_use_scroll_holder) {
|
||
$("#chat").scrollTop(scroll_holder);
|
||
is_use_scroll_holder = false;
|
||
}
|
||
});
|
||
|
||
$(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).css("background", "#600"); //sets the bg of the mes selected for deletion
|
||
var i = Number($(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
|
||
$(".mes[mesid='" + i + "']").css("background", "#600"); //sets the bg of the all msgs BELOW the selected .mes
|
||
$(".mes[mesid='" + i + "']")
|
||
.children(".del_checkbox")
|
||
.prop("checked", true);
|
||
i++;
|
||
//console.log(i);
|
||
}
|
||
});
|
||
|
||
$(document).on("click", "#user_avatar_block .avatar", setUserAvatar);
|
||
$(document).on("click", "#user_avatar_block .avatar_upload", function () {
|
||
$("#avatar_upload_overwrite").val("");
|
||
$("#avatar_upload_file").trigger('click');
|
||
});
|
||
$(document).on("click", "#user_avatar_block .set_persona_image", function () {
|
||
const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile');
|
||
|
||
if (!avatarId) {
|
||
console.log('no imgfile');
|
||
return;
|
||
}
|
||
|
||
$("#avatar_upload_overwrite").val(avatarId);
|
||
$("#avatar_upload_file").trigger('click');
|
||
});
|
||
$("#avatar_upload_file").on("change", uploadUserAvatar);
|
||
|
||
$(document).on("click", ".bg_example", async function () {
|
||
//when user clicks on a BG thumbnail...
|
||
const this_bgfile = $(this).attr("bgfile"); // this_bgfile = whatever they clicked
|
||
|
||
const customBg = window.getComputedStyle(document.getElementById('bg_custom')).backgroundImage;
|
||
|
||
// custom background is set. Do not override the layer below
|
||
if (customBg !== 'none') {
|
||
return;
|
||
}
|
||
|
||
// if clicked on upload button
|
||
if (!this_bgfile) {
|
||
return;
|
||
}
|
||
|
||
const backgroundUrl = `backgrounds/${this_bgfile}`;
|
||
|
||
// fetching to browser memory to reduce flicker
|
||
fetch(backgroundUrl).then(() => {
|
||
$("#bg1").css(
|
||
"background-image",
|
||
`url("${backgroundUrl}")`
|
||
);
|
||
setBackground(this_bgfile);
|
||
}).catch(() => {
|
||
console.log('Background could not be set: ' + backgroundUrl);
|
||
});
|
||
|
||
});
|
||
|
||
$(document).on('click', '.bg_example_edit', async function (e) {
|
||
e.stopPropagation();
|
||
const old_bg = $(this).attr('bgfile');
|
||
|
||
if (!old_bg) {
|
||
console.debug('no bgfile');
|
||
return;
|
||
}
|
||
|
||
const fileExtension = old_bg.split('.').pop();
|
||
const old_bg_extensionless = old_bg.replace(`.${fileExtension}`, '');
|
||
const new_bg_extensionless = await callPopup('<h3>Enter new background name:</h3>', 'input', old_bg_extensionless);
|
||
|
||
if (!new_bg_extensionless) {
|
||
console.debug('no new_bg_extensionless');
|
||
return;
|
||
}
|
||
|
||
const new_bg = `${new_bg_extensionless}.${fileExtension}`;
|
||
|
||
if (old_bg_extensionless === new_bg_extensionless) {
|
||
console.debug('new_bg === old_bg');
|
||
return;
|
||
}
|
||
|
||
const data = { old_bg, new_bg };
|
||
const response = await fetch('/renamebackground', {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify(data),
|
||
cache: 'no-cache',
|
||
});
|
||
|
||
if (response.ok) {
|
||
await getBackgrounds();
|
||
} else {
|
||
toastr.warning('Failed to rename background');
|
||
}
|
||
});
|
||
|
||
$(document).on("click", ".bg_example_cross", function (e) {
|
||
e.stopPropagation();
|
||
bg_file_for_del = $(this);
|
||
//$(this).parent().remove();
|
||
//delBackground(this_bgfile);
|
||
popup_type = "del_bg";
|
||
callPopup("<h3>Delete the background?</h3>");
|
||
});
|
||
|
||
$(document).on("click", ".PastChat_cross", function () {
|
||
chat_file_for_del = $(this).attr('file_name');
|
||
console.debug('detected cross click for' + chat_file_for_del);
|
||
popup_type = "del_chat";
|
||
callPopup("<h3>Delete the Chat File?</h3>");
|
||
});
|
||
|
||
$("#advanced_div").click(function () {
|
||
if (!is_advanced_char_open) {
|
||
is_advanced_char_open = true;
|
||
$("#character_popup").css("display", "flex");
|
||
$("#character_popup").css("opacity", 0.0);
|
||
$("#character_popup").transition({
|
||
opacity: 1.0,
|
||
duration: animation_duration,
|
||
easing: animation_easing,
|
||
});
|
||
} else {
|
||
is_advanced_char_open = false;
|
||
$("#character_popup").css("display", "none");
|
||
}
|
||
});
|
||
|
||
$("#character_cross").click(function () {
|
||
is_advanced_char_open = false;
|
||
$("#character_popup").transition({
|
||
opacity: 0,
|
||
duration: 200,
|
||
easing: animation_easing,
|
||
});
|
||
setTimeout(function () { $("#character_popup").css("display", "none"); }, 200);
|
||
});
|
||
|
||
$("#character_popup_ok").click(function () {
|
||
is_advanced_char_open = false;
|
||
$("#character_popup").css("display", "none");
|
||
});
|
||
|
||
$("#dialogue_popup_ok").click(async function (e) {
|
||
$("#shadow_popup").transition({
|
||
opacity: 0,
|
||
duration: 200,
|
||
easing: animation_easing,
|
||
});
|
||
setTimeout(function () {
|
||
$("#shadow_popup").css("display", "none");
|
||
$("#dialogue_popup").removeClass('large_dialogue_popup');
|
||
$("#dialogue_popup").removeClass('wide_dialogue_popup');
|
||
}, 200);
|
||
|
||
// $("#shadow_popup").css("opacity:", 0.0);
|
||
|
||
if (popup_type == 'avatarToCrop') {
|
||
dialogueResolve($("#avatarToCrop").data('cropper').getCroppedCanvas().toDataURL('image/jpeg'));
|
||
};
|
||
|
||
if (popup_type == "del_bg") {
|
||
delBackground(bg_file_for_del.attr("bgfile"));
|
||
bg_file_for_del.parent().remove();
|
||
}
|
||
if (popup_type == "del_chat") {
|
||
//close past chat popup
|
||
$("#select_chat_cross").click();
|
||
|
||
if (selected_group) {
|
||
await deleteGroupChat(selected_group, chat_file_for_del);
|
||
} else {
|
||
await delChat(chat_file_for_del);
|
||
}
|
||
|
||
//open the history view again after 100ms
|
||
//hide option popup menu
|
||
setTimeout(function () {
|
||
$("#option_select_chat").click();
|
||
$("#options").hide();
|
||
}, 2000);
|
||
}
|
||
if (popup_type == "del_ch") {
|
||
const deleteChats = !!$("#del_char_checkbox").prop("checked");
|
||
await handleDeleteCharacter(popup_type, this_chid, deleteChats);
|
||
eventSource.emit('characterDeleted', { id: this_chid, character: characters[this_chid] });
|
||
}
|
||
if (popup_type == "alternate_greeting" && menu_type !== "create") {
|
||
createOrEditCharacter();
|
||
}
|
||
if (popup_type === "del_group") {
|
||
const groupId = $("#dialogue_popup").data("group_id");
|
||
|
||
if (groupId) {
|
||
deleteGroup(groupId);
|
||
}
|
||
}
|
||
//Make a new chat for selected character
|
||
if (
|
||
popup_type == "new_chat" &&
|
||
(selected_group || this_chid !== undefined) &&
|
||
menu_type != "create"
|
||
) {
|
||
//Fix it; New chat doesn't create while open create character menu
|
||
clearChat();
|
||
chat.length = 0;
|
||
|
||
if (selected_group) {
|
||
await createNewGroupChat(selected_group);
|
||
}
|
||
else {
|
||
//RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime;
|
||
chat_metadata = {};
|
||
characters[this_chid].chat = name2 + " - " + humanizedDateTime();
|
||
$("#selected_chat_pole").val(characters[this_chid].chat);
|
||
await getChat();
|
||
await createOrEditCharacter();
|
||
}
|
||
}
|
||
|
||
rawPromptPopper.update();
|
||
$('#rawPromptPopup').hide();
|
||
|
||
if (dialogueResolve) {
|
||
if (popup_type == 'input') {
|
||
dialogueResolve($("#dialogue_popup_input").val());
|
||
$("#dialogue_popup_input").val('');
|
||
|
||
}
|
||
else {
|
||
dialogueResolve(true);
|
||
|
||
}
|
||
|
||
dialogueResolve = null;
|
||
}
|
||
});
|
||
|
||
$("#dialogue_popup_cancel").click(function (e) {
|
||
$("#shadow_popup").transition({
|
||
opacity: 0,
|
||
duration: 200,
|
||
easing: animation_easing,
|
||
});
|
||
setTimeout(function () {
|
||
$("#shadow_popup").css("display", "none");
|
||
$("#dialogue_popup").removeClass('large_dialogue_popup');
|
||
}, 200);
|
||
|
||
//$("#shadow_popup").css("opacity:", 0.0);
|
||
popup_type = "";
|
||
|
||
if (dialogueResolve) {
|
||
dialogueResolve(false);
|
||
dialogueResolve = null;
|
||
}
|
||
|
||
});
|
||
|
||
$("#add_bg_button").change(function () {
|
||
read_bg_load(this);
|
||
});
|
||
|
||
$("#add_avatar_button").change(function () {
|
||
read_avatar_load(this);
|
||
});
|
||
|
||
$("#form_create").submit(createOrEditCharacter);
|
||
|
||
$("#delete_button").on('click', function () {
|
||
popup_type = "del_ch";
|
||
callPopup(`
|
||
<h3>Delete the character?</h3>
|
||
<b>THIS IS PERMANENT!<br><br>
|
||
<label for="del_char_checkbox" class="checkbox_label justifyCenter">
|
||
<input type="checkbox" id="del_char_checkbox" />
|
||
<span>Also delete the chat files</span>
|
||
</label><br></b>`
|
||
);
|
||
});
|
||
|
||
//////// OPTIMIZED ALL CHAR CREATION/EDITING TEXTAREA LISTENERS ///////////////
|
||
|
||
$("#character_name_pole").on("input", function () {
|
||
if (menu_type == "create") {
|
||
create_save.name = $("#character_name_pole").val();
|
||
}
|
||
});
|
||
|
||
const elementsToUpdate = {
|
||
'#description_textarea': function () { create_save.description = $("#description_textarea").val(); },
|
||
'#creator_notes_textarea': function () { create_save.creator_notes = $("#creator_notes_textarea").val(); },
|
||
'#character_version_textarea': function () { create_save.character_version = $("#character_version_textarea").val(); },
|
||
'#system_prompt_textarea': function () { create_save.system_prompt = $("#system_prompt_textarea").val(); },
|
||
'#post_history_instructions_textarea': function () { create_save.post_history_instructions = $("#post_history_instructions_textarea").val(); },
|
||
'#creator_textarea': function () { create_save.creator = $("#creator_textarea").val(); },
|
||
'#tags_textarea': function () { create_save.tags = $("#tags_textarea").val(); },
|
||
'#personality_textarea': function () { create_save.personality = $("#personality_textarea").val(); },
|
||
'#scenario_pole': function () { create_save.scenario = $("#scenario_pole").val(); },
|
||
'#mes_example_textarea': function () { create_save.mes_example = $("#mes_example_textarea").val(); },
|
||
'#firstmessage_textarea': function () { create_save.first_message = $("#firstmessage_textarea").val(); },
|
||
'#talkativeness_slider': function () { create_save.talkativeness = $("#talkativeness_slider").val(); },
|
||
};
|
||
|
||
Object.keys(elementsToUpdate).forEach(function (id) {
|
||
$(id).on("input", function () {
|
||
if (menu_type == "create") {
|
||
elementsToUpdate[id]();
|
||
} else {
|
||
saveCharacterDebounced();
|
||
}
|
||
});
|
||
});
|
||
|
||
$("#favorite_button").on('click', function () {
|
||
updateFavButtonState(!fav_ch_checked);
|
||
if (menu_type != "create") {
|
||
saveCharacterDebounced();
|
||
}
|
||
});
|
||
|
||
/* $("#renameCharButton").on('click', renameCharacter); */
|
||
|
||
$(document).on("click", ".renameChatButton", async function () {
|
||
const old_filenamefull = $(this).closest('.select_chat_block_wrapper').find('.select_chat_block_filename').text();
|
||
const old_filename = old_filenamefull.replace('.jsonl', '');
|
||
|
||
const popupText = `<h3>Enter the new name for the chat:<h3>
|
||
<small>!!Using an existing filename will produce an error!!<br>
|
||
This will break the link between bookmark chats.<br>
|
||
No need to add '.jsonl' at the end.<br>
|
||
</small>`;
|
||
const newName = await callPopup(popupText, 'input', old_filename);
|
||
|
||
if (!newName || newName == old_filename) {
|
||
console.log('no new name found, aborting');
|
||
return;
|
||
}
|
||
|
||
const body = {
|
||
is_group: !!selected_group,
|
||
avatar_url: characters[this_chid]?.avatar,
|
||
original_file: `${old_filename}.jsonl`,
|
||
renamed_file: `${newName}.jsonl`,
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/renamechat', {
|
||
method: 'POST',
|
||
body: JSON.stringify(body),
|
||
headers: getRequestHeaders(),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Unsuccessful request.');
|
||
}
|
||
|
||
const data = response.json();
|
||
|
||
if (data.error) {
|
||
throw new Error('Server returned an error.');
|
||
}
|
||
|
||
if (selected_group) {
|
||
await renameGroupChat(selected_group, old_filename, newName);
|
||
}
|
||
else {
|
||
if (characters[this_chid].chat == old_filename) {
|
||
characters[this_chid].chat = newName;
|
||
await createOrEditCharacter();
|
||
}
|
||
}
|
||
|
||
await reloadCurrentChat();
|
||
|
||
await delay(250);
|
||
$("#option_select_chat").trigger('click');
|
||
$("#options").hide();
|
||
} catch {
|
||
await delay(500);
|
||
await callPopup('An error has occurred. Chat was not renamed.', 'text');
|
||
}
|
||
});
|
||
|
||
$(document).on("click", ".exportChatButton, .exportRawChatButton", async function () {
|
||
const format = $(this).data('format') || 'txt';
|
||
await saveChatConditional();
|
||
const filenamefull = $(this).closest('.select_chat_block_wrapper').find('.select_chat_block_filename').text();
|
||
console.log(`exporting ${filenamefull} in ${format} format`);
|
||
|
||
const filename = filenamefull.replace('.jsonl', '');
|
||
const body = {
|
||
is_group: !!selected_group,
|
||
avatar_url: characters[this_chid]?.avatar,
|
||
file: `${filename}.jsonl`,
|
||
exportfilename: `${filename}.${format}`,
|
||
format: format,
|
||
}
|
||
console.log(body);
|
||
try {
|
||
const response = await fetch('/exportchat', {
|
||
method: 'POST',
|
||
body: JSON.stringify(body),
|
||
headers: getRequestHeaders(),
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok) {
|
||
// display error message
|
||
console.log(data.message);
|
||
await delay(250);
|
||
toastr.error(`Error: ${data.message}`);
|
||
return;
|
||
} else {
|
||
const mimeType = format == 'txt' ? 'text/plain' : 'application/octet-stream';
|
||
// success, handle response data
|
||
console.log(data);
|
||
await delay(250);
|
||
toastr.success(data.message);
|
||
download(data.result, body.exportfilename, mimeType);
|
||
}
|
||
} catch (error) {
|
||
// display error message
|
||
console.log(`An error has occurred: ${error.message}`);
|
||
await delay(250);
|
||
toastr.error(`Error: ${error.message}`);
|
||
}
|
||
});
|
||
|
||
///////////////////////////////////////////////////////////////////////////////////
|
||
|
||
$("#api_button").click(function (e) {
|
||
if ($("#api_url_text").val() != "") {
|
||
let value = formatKoboldUrl(String($("#api_url_text").val()).trim());
|
||
|
||
if (!value) {
|
||
toastr.error('Please enter a valid URL.');
|
||
return;
|
||
}
|
||
|
||
$("#api_url_text").val(value);
|
||
api_server = value;
|
||
$("#api_loading").css("display", "inline-block");
|
||
$("#api_button").css("display", "none");
|
||
|
||
main_api = "kobold";
|
||
saveSettingsDebounced();
|
||
is_get_status = true;
|
||
is_api_button_press = true;
|
||
getStatus();
|
||
}
|
||
});
|
||
|
||
$("#use-mancer-api-checkbox").on("change", function (e) {
|
||
const enabled = $("#use-mancer-api-checkbox").prop("checked");
|
||
$("#mancer_api_subpanel").toggle(enabled);
|
||
$("#tgwebui_api_subpanel").toggle(!enabled);
|
||
|
||
api_use_mancer_webui = enabled;
|
||
saveSettingsDebounced();
|
||
getStatus();
|
||
|
||
if (enabled) {
|
||
loadMancerModels();
|
||
}
|
||
});
|
||
|
||
$("#api_button_textgenerationwebui").click(async function (e) {
|
||
const url_source = api_use_mancer_webui ? "#mancer_api_url_text" : "#textgenerationwebui_api_url_text";
|
||
if ($(url_source).val() != "") {
|
||
let value = formatTextGenURL(String($(url_source).val()).trim(), api_use_mancer_webui);
|
||
if (!value) {
|
||
callPopup("Please enter a valid URL.<br/>WebUI URLs should end with <tt>/api</tt><br/>Enable 'Relaxed API URLs' to allow other paths.", 'text');
|
||
return;
|
||
}
|
||
|
||
const mancer_key = String($("#api_key_mancer").val()).trim();
|
||
if (mancer_key.length) {
|
||
await writeSecret(SECRET_KEYS.MANCER, mancer_key);
|
||
}
|
||
|
||
$(url_source).val(value);
|
||
$("#api_loading_textgenerationwebui").css("display", "inline-block");
|
||
$("#api_button_textgenerationwebui").css("display", "none");
|
||
|
||
if (api_use_mancer_webui) {
|
||
textgenerationwebui_settings.streaming_url = value.replace("http", "ws") + "/v1/stream";
|
||
}
|
||
api_server_textgenerationwebui = value;
|
||
main_api = "textgenerationwebui";
|
||
saveSettingsDebounced();
|
||
is_get_status = true;
|
||
is_api_button_press = true;
|
||
getStatus();
|
||
}
|
||
});
|
||
|
||
var button = $('#options_button');
|
||
var menu = $('#options');
|
||
|
||
function showMenu() {
|
||
showBookmarksButtons();
|
||
menu.stop().fadeIn(250);
|
||
optionsPopper.update();
|
||
}
|
||
|
||
function hideMenu() {
|
||
menu.stop().fadeOut(250);
|
||
optionsPopper.update();
|
||
}
|
||
|
||
function isMouseOverButtonOrMenu() {
|
||
return menu.is(':hover') || button.is(':hover');
|
||
}
|
||
|
||
button.on('mouseenter click', function () { showMenu(); });
|
||
button.on('mouseleave', function () {
|
||
//delay to prevent menu hiding when mouse leaves button into menu
|
||
setTimeout(() => {
|
||
if (!isMouseOverButtonOrMenu()) { hideMenu(); }
|
||
}, 100)
|
||
});
|
||
menu.on('mouseleave', function () {
|
||
//delay to prevent menu hide when mouseleaves menu into button
|
||
setTimeout(() => {
|
||
if (!isMouseOverButtonOrMenu()) { hideMenu(); }
|
||
}, 100)
|
||
});
|
||
$(document).on('click', function () {
|
||
if (!isMouseOverButtonOrMenu() && menu.is(':visible')) { hideMenu(); }
|
||
});
|
||
|
||
/* $('#set_chat_scenario').on('click', setScenarioOverride); */
|
||
|
||
///////////// OPTIMIZED LISTENERS FOR LEFT SIDE OPTIONS POPUP MENU //////////////////////
|
||
$("#options [id]").on("click", function (event, customData) {
|
||
const fromSlashCommand = customData?.fromSlashCommand || false;
|
||
var id = $(this).attr("id");
|
||
|
||
if (id == "option_select_chat") {
|
||
if ((selected_group && !is_group_generating) || (this_chid !== undefined && !is_send_press) || fromSlashCommand) {
|
||
displayPastChats();
|
||
//this is just to avoid the shadow for past chat view when using /delchat
|
||
//however, the dialog popup still gets one..
|
||
if (!fromSlashCommand) {
|
||
console.log('displaying shadow')
|
||
$("#shadow_select_chat_popup").css("display", "block");
|
||
$("#shadow_select_chat_popup").css("opacity", 0.0);
|
||
$("#shadow_select_chat_popup").transition({
|
||
opacity: 1.0,
|
||
duration: animation_duration,
|
||
easing: animation_easing,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
else if (id == "option_start_new_chat") {
|
||
if ((selected_group || this_chid !== undefined) && !is_send_press) {
|
||
popup_type = "new_chat";
|
||
callPopup("<h3>Start new chat?</h3>");
|
||
}
|
||
}
|
||
|
||
else if (id == "option_regenerate") {
|
||
if (is_send_press == false) {
|
||
//hideSwipeButtons();
|
||
|
||
if (selected_group) {
|
||
regenerateGroup();
|
||
}
|
||
else {
|
||
is_send_press = true;
|
||
Generate("regenerate");
|
||
}
|
||
}
|
||
}
|
||
|
||
else if (id == "option_impersonate") {
|
||
if (is_send_press == false || fromSlashCommand) {
|
||
is_send_press = true;
|
||
Generate("impersonate");
|
||
}
|
||
}
|
||
|
||
else if (id == 'option_continue') {
|
||
if (is_send_press == false || fromSlashCommand) {
|
||
is_send_press = true;
|
||
Generate("continue");
|
||
}
|
||
}
|
||
|
||
else if (id == "option_delete_mes") {
|
||
setTimeout(openMessageDelete, animation_duration);
|
||
}
|
||
|
||
else if (id == "option_close_chat") {
|
||
if (is_send_press == false) {
|
||
clearChat();
|
||
chat.length = 0;
|
||
resetSelectedGroup();
|
||
setCharacterId(undefined);
|
||
setCharacterName('');
|
||
setActiveCharacter(null);
|
||
setActiveGroup(null);
|
||
this_edit_mes_id = undefined;
|
||
chat_metadata = {};
|
||
selected_button = "characters";
|
||
$("#rm_button_selected_ch").children("h2").text('');
|
||
select_rm_characters();
|
||
sendSystemMessage(system_message_types.WELCOME);
|
||
} else {
|
||
toastr.info("Please stop the message generation first.");
|
||
}
|
||
}
|
||
|
||
else if (id === "option_settings") {
|
||
//var checkBox = document.getElementById("waifuMode");
|
||
var topBar = document.getElementById("top-bar");
|
||
var topSettingsHolder = document.getElementById("top-settings-holder");
|
||
var divchat = document.getElementById("chat");
|
||
|
||
//if (checkBox.checked) {
|
||
if (topBar.style.display === "none") {
|
||
topBar.style.display = ""; // or "inline-block" if that's the original display value
|
||
topSettingsHolder.style.display = ""; // or "inline-block" if that's the original display value
|
||
|
||
divchat.style.borderRadius = "";
|
||
divchat.style.backgroundColor = "";
|
||
|
||
} else {
|
||
|
||
divchat.style.borderRadius = "10px"; // Adjust the value to control the roundness of the corners
|
||
divchat.style.backgroundColor = ""; // Set the background color to your preference
|
||
|
||
topBar.style.display = "none";
|
||
topSettingsHolder.style.display = "none";
|
||
}
|
||
//}
|
||
}
|
||
hideMenu();
|
||
});
|
||
|
||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
//functionality for the cancel delete messages button, reverts to normal display of input form
|
||
$("#dialogue_del_mes_cancel").click(function () {
|
||
$("#dialogue_del_mes").css("display", "none");
|
||
$("#send_form").css("display", css_send_form_display);
|
||
$(".del_checkbox").each(function () {
|
||
$(this).css("display", "none");
|
||
$(this).parent().children(".for_checkbox").css("display", "block");
|
||
$(this).parent().css("background", css_mes_bg);
|
||
$(this).prop("checked", false);
|
||
});
|
||
this_del_mes = 0;
|
||
console.debug('canceled del msgs, calling showswipesbtns');
|
||
showSwipeButtons();
|
||
is_delete_mode = false;
|
||
});
|
||
|
||
//confirms message deletion with the "ok" button
|
||
$("#dialogue_del_mes_ok").click(async function () {
|
||
$("#dialogue_del_mes").css("display", "none");
|
||
$("#send_form").css("display", css_send_form_display);
|
||
$(".del_checkbox").each(function () {
|
||
$(this).css("display", "none");
|
||
$(this).parent().children(".for_checkbox").css("display", "block");
|
||
$(this).parent().css("background", css_mes_bg);
|
||
$(this).prop("checked", false);
|
||
});
|
||
if (this_del_mes != 0) {
|
||
$(".mes[mesid='" + this_del_mes + "']")
|
||
.nextAll("div")
|
||
.remove();
|
||
$(".mes[mesid='" + this_del_mes + "']").remove();
|
||
chat.length = this_del_mes;
|
||
count_view_mes = this_del_mes;
|
||
await saveChatConditional();
|
||
var $textchat = $("#chat");
|
||
$textchat.scrollTop($textchat[0].scrollHeight);
|
||
eventSource.emit(event_types.MESSAGE_DELETED, chat.length);
|
||
}
|
||
this_del_mes = 0;
|
||
$('#chat .mes').last().addClass('last_mes');
|
||
$('#chat .mes').eq(-2).removeClass('last_mes');
|
||
console.debug('confirmed del msgs, calling showswipesbtns');
|
||
showSwipeButtons();
|
||
is_delete_mode = false;
|
||
});
|
||
|
||
$("#settings_perset").change(function () {
|
||
if ($("#settings_perset").find(":selected").val() != "gui") {
|
||
preset_settings = $("#settings_perset").find(":selected").text();
|
||
const preset = koboldai_settings[koboldai_setting_names[preset_settings]];
|
||
loadKoboldSettings(preset);
|
||
|
||
setGenerationParamsFromPreset(preset);
|
||
|
||
$("#range_block").find('input').prop("disabled", false);
|
||
$("#range_block").css("opacity", 1.0);
|
||
|
||
$("#kobold-advanced-config").find('input').prop("disabled", false);
|
||
$("#kobold-advanced-config").css('opacity', 1.0);
|
||
|
||
$("#kobold_order").sortable("enable");
|
||
} else {
|
||
//$('.button').disableSelection();
|
||
preset_settings = "gui";
|
||
$("#range_block").find('input').prop("disabled", true);
|
||
$("#range_block").css("opacity", 0.5);
|
||
|
||
$("#kobold-advanced-config").find('input').prop("disabled", true);
|
||
$("#kobold-advanced-config").css('opacity', 0.5);
|
||
|
||
$("#kobold_order").sortable("disable");
|
||
}
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$("#settings_perset_novel").change(function () {
|
||
nai_settings.preset_settings_novel = $("#settings_perset_novel")
|
||
.find(":selected")
|
||
.text();
|
||
|
||
const preset = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]];
|
||
loadNovelPreset(preset);
|
||
amount_gen = Number($("#amount_gen").val());
|
||
max_context = Number($("#max_context").val());
|
||
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$("#main_api").change(function () {
|
||
is_get_status = false;
|
||
is_get_status_novel = false;
|
||
setOpenAIOnlineStatus(false);
|
||
online_status = "no_connection";
|
||
checkOnlineStatus();
|
||
changeMainAPI();
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
////////////////// OPTIMIZED RANGE SLIDER LISTENERS////////////////
|
||
|
||
var sliderLocked = true;
|
||
var sliderTimer;
|
||
|
||
$("input[type='range']").on("touchstart", function () {
|
||
// Unlock the slider after 300ms
|
||
setTimeout(function () {
|
||
sliderLocked = false;
|
||
$(this).css('background-color', 'var(--SmartThemeQuoteColor)');
|
||
}.bind(this), 300);
|
||
});
|
||
|
||
$("input[type='range']").on("touchend", function () {
|
||
clearTimeout(sliderTimer);
|
||
$(this).css('background-color', '');
|
||
sliderLocked = true;
|
||
});
|
||
|
||
$("input[type='range']").on("touchmove", function (event) {
|
||
if (sliderLocked) {
|
||
event.preventDefault();
|
||
}
|
||
});
|
||
|
||
const sliders = [
|
||
{
|
||
sliderId: "#amount_gen",
|
||
counterId: "#amount_gen_counter",
|
||
format: (val) => `${val}`,
|
||
setValue: (val) => { amount_gen = Number(val); },
|
||
},
|
||
{
|
||
sliderId: "#max_context",
|
||
counterId: "#max_context_counter",
|
||
format: (val) => `${val}`,
|
||
setValue: (val) => { max_context = Number(val); },
|
||
}
|
||
];
|
||
|
||
sliders.forEach(slider => {
|
||
$(document).on("input", slider.sliderId, function () {
|
||
const value = $(this).val();
|
||
const formattedValue = slider.format(value);
|
||
slider.setValue(value);
|
||
$(slider.counterId).text(formattedValue);
|
||
saveSettingsDebounced();
|
||
});
|
||
});
|
||
|
||
//////////////////////////////////////////////////////////////
|
||
|
||
$("#select_chat_cross").click(function () {
|
||
$("#shadow_select_chat_popup").transition({
|
||
opacity: 0,
|
||
duration: 200,
|
||
easing: animation_easing,
|
||
});
|
||
setTimeout(function () { $("#shadow_select_chat_popup").css("display", "none"); }, 200);
|
||
//$("#shadow_select_chat_popup").css("display", "none");
|
||
$("#load_select_chat_div").css("display", "block");
|
||
});
|
||
|
||
if (navigator.clipboard === undefined) {
|
||
// No clipboard support
|
||
$(".mes_copy").remove();
|
||
}
|
||
else {
|
||
$(document).on("pointerup", ".mes_copy", function () {
|
||
if (this_chid !== undefined || selected_group) {
|
||
const message = $(this).closest(".mes");
|
||
|
||
if (message.data("isSystem")) {
|
||
return;
|
||
}
|
||
try {
|
||
var edit_mes_id = $(this).closest(".mes").attr("mesid");
|
||
var text = chat[edit_mes_id]["mes"];
|
||
navigator.clipboard.writeText(text);
|
||
toastr.info('Copied!', '', { timeOut: 2000 });
|
||
} catch (err) {
|
||
console.error('Failed to copy: ', err);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
$(document).on("pointerup", ".mes_prompt", function () {
|
||
let mesIdForItemization = $(this).closest('.mes').attr('mesId');
|
||
console.log(`looking for mesID: ${mesIdForItemization}`);
|
||
if (itemizedPrompts.length !== undefined && itemizedPrompts.length !== 0) {
|
||
promptItemize(itemizedPrompts, mesIdForItemization);
|
||
}
|
||
})
|
||
|
||
$(document).on("pointerup", "#copyPromptToClipboard", function () {
|
||
let rawPrompt = itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt;
|
||
let rawPromptValues = rawPrompt;
|
||
|
||
if (Array.isArray(rawPrompt)) {
|
||
rawPromptValues = rawPrompt.map(x => x.content).join('\n');
|
||
}
|
||
|
||
navigator.clipboard.writeText(rawPromptValues);
|
||
toastr.info('Copied!', '', { timeOut: 2000 });
|
||
});
|
||
|
||
$(document).on("pointerup", "#showRawPrompt", function () {
|
||
//console.log(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt);
|
||
console.log(PromptArrayItemForRawPromptDisplay);
|
||
console.log(itemizedPrompts);
|
||
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, '<br>');
|
||
$("#rawPromptWrapper").text(rawPromptValues);
|
||
rawPromptPopper.update();
|
||
$('#rawPromptPopup').toggle();
|
||
})
|
||
|
||
//********************
|
||
//***Message Editor***
|
||
$(document).on("click", ".mes_edit", async function () {
|
||
if (this_chid !== undefined || selected_group) {
|
||
// Previously system messages we're allowed to be edited
|
||
/*const message = $(this).closest(".mes");
|
||
|
||
if (message.data("isSystem")) {
|
||
return;
|
||
}*/
|
||
|
||
let chatScrollPosition = $("#chat").scrollTop();
|
||
if (this_edit_mes_id !== undefined) {
|
||
let mes_edited = $(`#chat [mesid="${this_edit_mes_id}"]`).find(".mes_edit_done");
|
||
if (Number(edit_mes_id) == count_view_mes - 1) { //if the generating swipe (...)
|
||
if (chat[edit_mes_id]['swipe_id'] !== undefined) {
|
||
if (chat[edit_mes_id]['swipes'].length === chat[edit_mes_id]['swipe_id']) {
|
||
run_edit = false;
|
||
}
|
||
}
|
||
if (run_edit) {
|
||
hideSwipeButtons();
|
||
}
|
||
}
|
||
await messageEditDone(mes_edited);
|
||
}
|
||
$(this).closest(".mes_block").find(".mes_text").empty();
|
||
$(this).closest(".mes_block").find(".mes_buttons").css("display", "none");
|
||
$(this).closest(".mes_block").find(".mes_edit_buttons").css("display", "inline-flex");
|
||
var edit_mes_id = $(this).closest(".mes").attr("mesid");
|
||
this_edit_mes_id = edit_mes_id;
|
||
|
||
var text = chat[edit_mes_id]["mes"];
|
||
if (chat[edit_mes_id]["is_user"]) {
|
||
this_edit_mes_chname = name1;
|
||
} else if (chat[edit_mes_id]["force_avatar"]) {
|
||
this_edit_mes_chname = chat[edit_mes_id]["name"];
|
||
} else {
|
||
this_edit_mes_chname = name2;
|
||
}
|
||
if (power_user.trim_spaces) {
|
||
text = text.trim();
|
||
}
|
||
$(this)
|
||
.closest(".mes_block")
|
||
.find(".mes_text")
|
||
.append(
|
||
`<textarea id='curEditTextarea' class='edit_textarea' style='max-width:auto;'></textarea>`
|
||
);
|
||
$('#curEditTextarea').val(text);
|
||
let edit_textarea = $(this)
|
||
.closest(".mes_block")
|
||
.find(".edit_textarea");
|
||
edit_textarea.height(0);
|
||
edit_textarea.height(edit_textarea[0].scrollHeight);
|
||
edit_textarea.focus();
|
||
edit_textarea[0].setSelectionRange( //this sets the cursor at the end of the text
|
||
edit_textarea.val().length,
|
||
edit_textarea.val().length
|
||
);
|
||
if (this_edit_mes_id == count_view_mes - 1) {
|
||
$("#chat").scrollTop(chatScrollPosition);
|
||
}
|
||
|
||
updateEditArrowClasses();
|
||
}
|
||
});
|
||
|
||
$(document).on('input', '#curEditTextarea', function () {
|
||
if (power_user.auto_save_msg_edits === true) {
|
||
messageEditAuto($(this));
|
||
}
|
||
})
|
||
|
||
$(document).on("click", ".extraMesButtonsHint", function (e) {
|
||
const elmnt = e.target;
|
||
$(elmnt).transition({
|
||
opacity: 0,
|
||
duration: 150,
|
||
easing: 'ease-in-out',
|
||
});
|
||
setTimeout(function () {
|
||
$(elmnt).hide();
|
||
$(elmnt).siblings(".extraMesButtons").css('opcacity', '0');
|
||
$(elmnt).siblings(".extraMesButtons").css('display', 'flex');
|
||
$(elmnt).siblings(".extraMesButtons").transition({
|
||
opacity: 1,
|
||
duration: 150,
|
||
easing: 'ease-in-out',
|
||
});
|
||
}, 150);
|
||
})
|
||
|
||
$(document).on("click", ".mes_edit_cancel", function () {
|
||
let text = chat[this_edit_mes_id]["mes"];
|
||
|
||
$(this).closest(".mes_block").find(".mes_text").empty();
|
||
$(this).closest(".mes_edit_buttons").css("display", "none");
|
||
$(this).closest(".mes_block").find(".mes_buttons").css("display", "");
|
||
$(this)
|
||
.closest(".mes_block")
|
||
.find(".mes_text")
|
||
.append(messageFormatting(
|
||
text,
|
||
this_edit_mes_chname,
|
||
chat[this_edit_mes_id].is_system,
|
||
chat[this_edit_mes_id].is_user,
|
||
));
|
||
appendImageToMessage(chat[this_edit_mes_id], $(this).closest(".mes"));
|
||
addCopyToCodeBlocks($(this).closest(".mes"));
|
||
this_edit_mes_id = undefined;
|
||
});
|
||
|
||
$(document).on("click", ".mes_edit_up", async function () {
|
||
if (is_send_press || this_edit_mes_id <= 0) {
|
||
return;
|
||
}
|
||
|
||
hideSwipeButtons();
|
||
const targetId = Number(this_edit_mes_id) - 1;
|
||
const target = $(`#chat .mes[mesid="${targetId}"]`);
|
||
const root = $(this).closest('.mes');
|
||
|
||
if (root.length === 0 || target.length === 0) {
|
||
return;
|
||
}
|
||
|
||
root.insertBefore(target);
|
||
|
||
target.attr("mesid", this_edit_mes_id);
|
||
root.attr("mesid", targetId);
|
||
|
||
const temp = chat[targetId];
|
||
chat[targetId] = chat[this_edit_mes_id];
|
||
chat[this_edit_mes_id] = temp;
|
||
|
||
this_edit_mes_id = targetId;
|
||
updateViewMessageIds();
|
||
await saveChatConditional();
|
||
showSwipeButtons();
|
||
});
|
||
|
||
$(document).on("click", ".mes_edit_down", async function () {
|
||
if (is_send_press || this_edit_mes_id >= chat.length - 1) {
|
||
return;
|
||
}
|
||
|
||
hideSwipeButtons();
|
||
const targetId = Number(this_edit_mes_id) + 1;
|
||
const target = $(`#chat .mes[mesid="${targetId}"]`);
|
||
const root = $(this).closest('.mes');
|
||
|
||
if (root.length === 0 || target.length === 0) {
|
||
return;
|
||
}
|
||
|
||
root.insertAfter(target);
|
||
|
||
target.attr("mesid", this_edit_mes_id);
|
||
root.attr("mesid", targetId);
|
||
|
||
const temp = chat[targetId];
|
||
chat[targetId] = chat[this_edit_mes_id];
|
||
chat[this_edit_mes_id] = temp;
|
||
|
||
this_edit_mes_id = targetId;
|
||
updateViewMessageIds();
|
||
await saveChatConditional();
|
||
showSwipeButtons();
|
||
});
|
||
|
||
$(document).on("click", ".mes_edit_copy", async function () {
|
||
const confirmation = await callPopup('Create a copy of this message?', 'confirm');
|
||
if (!confirmation) {
|
||
return;
|
||
}
|
||
|
||
hideSwipeButtons();
|
||
let oldScroll = $('#chat')[0].scrollTop;
|
||
const clone = JSON.parse(JSON.stringify(chat[this_edit_mes_id])); // quick and dirty clone
|
||
clone.send_date = Date.now();
|
||
clone.mes = $(this).closest(".mes").find('.edit_textarea').val();
|
||
|
||
if (power_user.trim_spaces) {
|
||
clone.mes = clone.mes.trim();
|
||
}
|
||
|
||
chat.splice(Number(this_edit_mes_id) + 1, 0, clone);
|
||
addOneMessage(clone, { insertAfter: this_edit_mes_id });
|
||
|
||
updateViewMessageIds();
|
||
await saveChatConditional();
|
||
$('#chat')[0].scrollTop = oldScroll;
|
||
showSwipeButtons();
|
||
});
|
||
|
||
$(document).on("click", ".mes_edit_delete", async function (event, customData) {
|
||
const fromSlashCommand = customData?.fromSlashCommand || false;
|
||
const swipeExists = (!Array.isArray(chat[this_edit_mes_id].swipes) || chat[this_edit_mes_id].swipes.length <= 1 || chat[this_edit_mes_id].is_user || parseInt(this_edit_mes_id) !== chat.length - 1);
|
||
if (power_user.confirm_message_delete && fromSlashCommand !== true) {
|
||
const confirmation = swipeExists ? await callPopup("Are you sure you want to delete this message?", 'confirm')
|
||
: await callPopup("<h3>Delete this...</h3> <select id='del_type'><option value='swipe'>Swipe</option><option value='message'>Message</option></select>", 'confirm')
|
||
if (!confirmation) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
const mes = $(this).closest(".mes");
|
||
|
||
if (!mes) {
|
||
return;
|
||
}
|
||
|
||
if ($('#del_type').val() === 'swipe') {
|
||
const swipe_id = chat[this_edit_mes_id]['swipe_id'];
|
||
chat[this_edit_mes_id]['swipes'].splice(swipe_id, 1);
|
||
if (swipe_id > 0) {
|
||
$('.swipe_left:last').click();
|
||
} else {
|
||
$('.swipe_right:last').click()
|
||
}
|
||
} else {
|
||
chat.splice(this_edit_mes_id, 1);
|
||
mes.remove();
|
||
count_view_mes--;
|
||
}
|
||
|
||
this_edit_mes_id = undefined;
|
||
|
||
updateViewMessageIds();
|
||
await saveChatConditional();
|
||
|
||
eventSource.emit(event_types.MESSAGE_DELETED, count_view_mes);
|
||
|
||
hideSwipeButtons();
|
||
showSwipeButtons();
|
||
});
|
||
|
||
$(document).on("click", ".mes_edit_done", async function () {
|
||
await messageEditDone($(this));
|
||
});
|
||
|
||
$("#your_name_button").click(function () {
|
||
setUserName($('#your_name').val());
|
||
});
|
||
|
||
$('#sync_name_button').on('click', async function () {
|
||
const confirmation = await callPopup(`<h3>Are you sure?</h3>All user-sent messages in this chat will be attributed to ${name1}.`, 'confirm');
|
||
|
||
if (!confirmation) {
|
||
return;
|
||
}
|
||
|
||
for (const mes of chat) {
|
||
if (mes.is_user) {
|
||
mes.name = name1;
|
||
mes.force_avatar = getUserAvatar(user_avatar);
|
||
}
|
||
}
|
||
|
||
await saveChatConditional();
|
||
await reloadCurrentChat();
|
||
});
|
||
//Select chat
|
||
|
||
$("#api_button_novel").on('click', async function (e) {
|
||
e.stopPropagation();
|
||
const api_key_novel = String($("#api_key_novel").val()).trim();
|
||
|
||
if (api_key_novel.length) {
|
||
await writeSecret(SECRET_KEYS.NOVEL, api_key_novel);
|
||
}
|
||
|
||
if (!secret_state[SECRET_KEYS.NOVEL]) {
|
||
console.log('No secret key saved for NovelAI');
|
||
return;
|
||
}
|
||
|
||
$("#api_loading_novel").css("display", "inline-block");
|
||
$("#api_button_novel").css("display", "none");
|
||
is_get_status_novel = true;
|
||
is_api_button_press_novel = true;
|
||
// Check near immediately rather than waiting for up to 90s
|
||
setTimeout(getStatusNovel, 10);
|
||
});
|
||
|
||
//**************************CHARACTER IMPORT EXPORT*************************//
|
||
$("#character_import_button").click(function () {
|
||
$("#character_import_file").click();
|
||
});
|
||
|
||
$("#character_import_file").on("change", function (e) {
|
||
$("#rm_info_avatar").html("");
|
||
if (!e.target.files.length) {
|
||
return;
|
||
}
|
||
|
||
for (const file of e.target.files) {
|
||
importCharacter(file);
|
||
}
|
||
});
|
||
|
||
$("#export_button").on('click', function (e) {
|
||
$('#export_format_popup').toggle();
|
||
exportPopper.update();
|
||
});
|
||
|
||
$(document).on('click', '.export_format', async function () {
|
||
const format = $(this).data('format');
|
||
|
||
if (!format) {
|
||
return;
|
||
}
|
||
|
||
// Save before exporting
|
||
await createOrEditCharacter();
|
||
const body = { format, avatar_url: characters[this_chid].avatar };
|
||
|
||
const response = await fetch('/exportcharacter', {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify(body),
|
||
});
|
||
|
||
if (response.ok) {
|
||
const filename = characters[this_chid].avatar.replace('.png', `.${format}`);
|
||
const blob = await response.blob();
|
||
const a = document.createElement("a");
|
||
a.href = URL.createObjectURL(blob);
|
||
a.setAttribute("download", filename);
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
}
|
||
|
||
|
||
$('#export_format_popup').hide();
|
||
});
|
||
//**************************CHAT IMPORT EXPORT*************************//
|
||
$("#chat_import_button").click(function () {
|
||
$("#chat_import_file").click();
|
||
});
|
||
|
||
$("#chat_import_file").on("change", async function (e) {
|
||
var file = e.target.files[0];
|
||
|
||
if (!file) {
|
||
return;
|
||
}
|
||
|
||
var ext = file.name.match(/\.(\w+)$/);
|
||
if (
|
||
!ext ||
|
||
(ext[1].toLowerCase() != "json" && ext[1].toLowerCase() != "jsonl")
|
||
) {
|
||
return;
|
||
}
|
||
|
||
if (selected_group && file.name.endsWith('.json')) {
|
||
toastr.warning("Only SillyTavern's own format is supported for group chat imports. Sorry!");
|
||
return;
|
||
}
|
||
|
||
var format = ext[1].toLowerCase();
|
||
$("#chat_import_file_type").val(format);
|
||
|
||
var formData = new FormData($("#form_import_chat").get(0));
|
||
formData.append('user_name', name1);
|
||
$("#select_chat_div").html("");
|
||
$("#load_select_chat_div").css("display", "block");
|
||
|
||
if (selected_group) {
|
||
await importGroupChat(formData);
|
||
} else {
|
||
await importCharacterChat(formData);
|
||
}
|
||
});
|
||
|
||
$("#rm_button_group_chats").click(function () {
|
||
selected_button = "group_chats";
|
||
select_group_chats();
|
||
});
|
||
|
||
$("#rm_button_back_from_group").click(function () {
|
||
selected_button = "characters";
|
||
select_rm_characters();
|
||
});
|
||
|
||
$("#dupe_button").click(async function () {
|
||
await DupeChar();
|
||
});
|
||
|
||
$(document).on("click", ".select_chat_block, .bookmark_link, .mes_bookmark", async function () {
|
||
let file_name = $(this).hasClass('mes_bookmark')
|
||
? $(this).closest('.mes').attr('bookmark_link')
|
||
: $(this).attr("file_name").replace(".jsonl", "");
|
||
|
||
if (!file_name) {
|
||
return;
|
||
}
|
||
|
||
if (selected_group) {
|
||
await openGroupChat(selected_group, file_name);
|
||
} else {
|
||
await openCharacterChat(file_name);
|
||
}
|
||
|
||
$("#shadow_select_chat_popup").css("display", "none");
|
||
$("#load_select_chat_div").css("display", "block");
|
||
});
|
||
|
||
$(document).on("click", ".mes_create_bookmark", async function () {
|
||
var selected_mes_id = $(this).closest(".mes").attr("mesid");
|
||
if (selected_mes_id !== undefined) {
|
||
createNewBookmark(selected_mes_id);
|
||
}
|
||
});
|
||
|
||
$(document).on("click", ".mes_create_branch", async function () {
|
||
var selected_mes_id = $(this).closest(".mes").attr("mesid");
|
||
if (selected_mes_id !== undefined) {
|
||
branchChat(selected_mes_id);
|
||
}
|
||
});
|
||
|
||
$(document).on("click", ".mes_stop", function () {
|
||
if (streamingProcessor) {
|
||
streamingProcessor.abortController.abort();
|
||
streamingProcessor.isStopped = true;
|
||
streamingProcessor.onStopStreaming();
|
||
streamingProcessor = null;
|
||
}
|
||
if (abortController) {
|
||
abortController.abort();
|
||
hideStopButton();
|
||
}
|
||
eventSource.emit(event_types.GENERATION_STOPPED);
|
||
});
|
||
|
||
$('.drawer-toggle').on('click', function () {
|
||
var icon = $(this).find('.drawer-icon');
|
||
var drawer = $(this).parent().find('.drawer-content');
|
||
if (drawer.hasClass('resizing')) { return }
|
||
var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
|
||
let targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
|
||
const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
|
||
|
||
if (!drawerWasOpenAlready) { //to open the drawer
|
||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, "swing", async function () {
|
||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||
});
|
||
$('.openIcon').toggleClass('closedIcon openIcon');
|
||
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
|
||
icon.toggleClass('openIcon closedIcon');
|
||
drawer.toggleClass('openDrawer closedDrawer');
|
||
|
||
//console.log(targetDrawerID);
|
||
if (targetDrawerID === 'right-nav-panel') {
|
||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle({
|
||
duration: 200,
|
||
easing: "swing",
|
||
start: function () {
|
||
jQuery(this).css('display', 'flex'); //flex needed to make charlist scroll
|
||
},
|
||
complete: async function () {
|
||
favsToHotswap();
|
||
await delay(50);
|
||
$(this).closest('.drawer-content').removeClass('resizing');
|
||
$("#rm_print_characters_block").trigger("scroll");
|
||
}
|
||
})
|
||
} else {
|
||
$(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle(200, "swing", async function () {
|
||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||
});
|
||
}
|
||
|
||
// Set the height of "autoSetHeight" textareas within the drawer to their scroll height
|
||
$(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(function () {
|
||
resetScrollHeight($(this));
|
||
});
|
||
|
||
} else if (drawerWasOpenAlready) { //to close manually
|
||
icon.toggleClass('closedIcon openIcon');
|
||
|
||
if (pinnedDrawerClicked) {
|
||
$(drawer).addClass('resizing').slideToggle(200, "swing", async function () {
|
||
await delay(50); $(this).removeClass('resizing');
|
||
});
|
||
}
|
||
else {
|
||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, "swing", async function () {
|
||
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
|
||
});
|
||
}
|
||
|
||
drawer.toggleClass('closedDrawer openDrawer');
|
||
}
|
||
});
|
||
|
||
$("html").on('touchstart mousedown', function (e) {
|
||
var clickTarget = $(e.target);
|
||
|
||
if ($('#export_format_popup').is(':visible')
|
||
&& clickTarget.closest('#export_button').length == 0
|
||
&& clickTarget.closest('#export_format_popup').length == 0) {
|
||
$('#export_format_popup').hide();
|
||
}
|
||
|
||
const forbiddenTargets = [
|
||
'#character_cross',
|
||
'#avatar-and-name-block',
|
||
'#shadow_popup',
|
||
'#world_popup',
|
||
'.ui-widget',
|
||
'.text_pole',
|
||
'#toast-container',
|
||
'.select2-results',
|
||
];
|
||
for (const id of forbiddenTargets) {
|
||
if (clickTarget.closest(id).length > 0) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
var targetParentHasOpenDrawer = clickTarget.parents('.openDrawer').length;
|
||
if (clickTarget.hasClass('drawer-icon') == false && !clickTarget.hasClass('openDrawer')) {
|
||
if (jQuery.find('.openDrawer').length !== 0) {
|
||
if (targetParentHasOpenDrawer === 0) {
|
||
//console.log($('.openDrawer').not('.pinnedOpen').length);
|
||
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, "swing", function () {
|
||
$(this).closest('.drawer-content').removeClass('resizing')
|
||
});
|
||
$('.openIcon').toggleClass('closedIcon openIcon');
|
||
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
|
||
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
$(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');
|
||
$(this).closest('.inline-drawer').find('.inline-drawer-content').stop().slideToggle();
|
||
|
||
// Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height
|
||
$(this).closest('.inline-drawer').find('.inline-drawer-content textarea.autoSetHeight').each(function () {
|
||
resetScrollHeight($(this));
|
||
});
|
||
});
|
||
|
||
$(document).on('click', '.mes .avatar', function () {
|
||
|
||
//console.log(isMobile());
|
||
//console.log($('body').hasClass('waifuMode'));
|
||
|
||
/* if (isMobile() === true && !$('body').hasClass('waifuMode')) {
|
||
console.debug('saw mobile regular mode, returning');
|
||
return;
|
||
} else { console.debug('saw valid env for zoomed display') } */
|
||
|
||
let thumbURL = $(this).children('img').attr('src');
|
||
let charsPath = '/characters/'
|
||
let targetAvatarImg = thumbURL.substring(thumbURL.lastIndexOf("=") + 1);
|
||
let charname = targetAvatarImg.replace('.png', '');
|
||
|
||
// Remove existing zoomed avatars for characters that are not the clicked character when moving UI is not enabled
|
||
if (!power_user.movingUI) {
|
||
$('.zoomed_avatar').each(function () {
|
||
const currentForChar = $(this).attr('forChar');
|
||
if (currentForChar !== charname && typeof currentForChar !== 'undefined') {
|
||
console.debug(`Removing zoomed avatar for character: ${currentForChar}`);
|
||
$(this).remove();
|
||
}
|
||
});
|
||
}
|
||
|
||
let avatarSrc = isDataURL(thumbURL) ? thumbURL : charsPath + targetAvatarImg;
|
||
if ($(`.zoomed_avatar[forChar="${charname}"]`).length) {
|
||
console.debug('removing container as it already existed')
|
||
$(`.zoomed_avatar[forChar="${charname}"]`).remove();
|
||
} else {
|
||
console.debug('making new container from template')
|
||
const template = $('#zoomed_avatar_template').html();
|
||
const newElement = $(template);
|
||
newElement.attr('forChar', charname);
|
||
newElement.attr('id', `zoomFor_${charname}`);
|
||
newElement.addClass('draggable');
|
||
newElement.find('.drag-grabber').attr('id', `zoomFor_${charname}header`);
|
||
|
||
$('body').append(newElement);
|
||
if ($(this).parent().parent().attr('is_user') == 'true') { //handle user avatars
|
||
$(`.zoomed_avatar[forChar="${charname}"] img`).attr('src', thumbURL);
|
||
} else if ($(this).parent().parent().attr('is_system') == 'true') { //handle system avatars
|
||
$(`.zoomed_avatar[forChar="${charname}"] img`).attr('src', thumbURL);
|
||
} else if ($(this).parent().parent().attr('is_user') == 'false') { //handle char avatars
|
||
$(`.zoomed_avatar[forChar="${charname}"] img`).attr('src', avatarSrc);
|
||
}
|
||
loadMovingUIState();
|
||
$(`.zoomed_avatar[forChar="${charname}"]`).css('display', 'block');
|
||
dragElement(newElement)
|
||
|
||
$(`.zoomed_avatar[forChar="${charname}"] img`).on('dragstart', (e) => {
|
||
console.log('saw drag on avatar!');
|
||
e.preventDefault();
|
||
return false;
|
||
});
|
||
}
|
||
});
|
||
|
||
$(document).on('click', '#OpenAllWIEntries', function () {
|
||
$("#world_popup_entries_list").children().find('.down').click()
|
||
});
|
||
$(document).on('click', '#CloseAllWIEntries', function () {
|
||
$("#world_popup_entries_list").children().find('.up').click()
|
||
});
|
||
$(document).on('click', '.open_alternate_greetings', openAlternateGreetings);
|
||
/* $('#set_character_world').on('click', openCharacterWorldPopup); */
|
||
|
||
$(document).keyup(function (e) {
|
||
if (e.key === "Escape") {
|
||
if (power_user.auto_save_msg_edits === false) {
|
||
closeMessageEditor();
|
||
$("#send_textarea").focus();
|
||
}
|
||
if (power_user.auto_save_msg_edits === true) {
|
||
$(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).click()
|
||
$("#send_textarea").focus();
|
||
}
|
||
if (!this_edit_mes_id && $('#mes_stop').is(':visible')) {
|
||
$('#mes_stop').trigger('click');
|
||
if (chat.length && Array.isArray(chat[chat.length - 1].swipes) && chat[chat.length - 1].swipe_id == chat[chat.length - 1].swipes.length) {
|
||
$('.last_mes .swipe_left').trigger('click');
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
$("#bg-filter").on("input", function () {
|
||
const filterValue = String($(this).val()).toLowerCase();
|
||
$("#bg_menu_content > div").each(function () {
|
||
const $bgContent = $(this);
|
||
if ($bgContent.attr("title").toLowerCase().includes(filterValue)) {
|
||
$bgContent.show();
|
||
} else {
|
||
$bgContent.hide();
|
||
}
|
||
});
|
||
});
|
||
|
||
$("#char-management-dropdown").on('change', async (e) => {
|
||
let target = $(e.target.selectedOptions).attr('id');
|
||
switch (target) {
|
||
case 'set_character_world':
|
||
openCharacterWorldPopup();
|
||
break;
|
||
case 'set_chat_scenario':
|
||
setScenarioOverride();
|
||
break;
|
||
case 'renameCharButton':
|
||
renameCharacter();
|
||
break;
|
||
/*case 'dupe_button':
|
||
DupeChar();
|
||
break;
|
||
case 'export_button':
|
||
$('#export_format_popup').toggle();
|
||
exportPopper.update();
|
||
break;
|
||
*/
|
||
case 'import_character_info':
|
||
await importEmbeddedWorldInfo();
|
||
saveCharacterDebounced();
|
||
break;
|
||
/*case 'delete_button':
|
||
popup_type = "del_ch";
|
||
callPopup(`
|
||
<h3>Delete the character?</h3>
|
||
<b>THIS IS PERMANENT!<br><br>
|
||
THIS WILL ALSO DELETE ALL<br>
|
||
OF THE CHARACTER'S CHAT FILES.<br><br></b>`
|
||
);
|
||
break;*/
|
||
default:
|
||
eventSource.emit('charManagementDropdown', target);
|
||
}
|
||
$("#char-management-dropdown").prop('selectedIndex', 0);
|
||
});
|
||
|
||
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
|
||
$(document).on('click', '.mes_img_delete', deleteMessageImage);
|
||
|
||
$(window).on('beforeunload', () => {
|
||
cancelTtsPlay();
|
||
if (streamingProcessor) {
|
||
console.log('Page reloaded. Aborting streaming...');
|
||
streamingProcessor.abortController.abort();
|
||
}
|
||
});
|
||
|
||
$(document).on('input', '.range-block-counter div[contenteditable="true"]', function () {
|
||
const caretPosition = saveCaretPosition($(this).get(0));
|
||
const myText = $(this).text().trim();
|
||
$(this).text(myText); // trim line breaks and spaces
|
||
const masterSelector = $(this).data('for');
|
||
const masterElement = document.getElementById(masterSelector);
|
||
|
||
if (masterElement == null) {
|
||
console.error('Master input element not found for the editable label', masterSelector);
|
||
return;
|
||
}
|
||
|
||
const myValue = Number(myText);
|
||
|
||
if (Number.isNaN(myValue)) {
|
||
console.warn('Label input is not a valid number. Resetting the value', myText);
|
||
$(masterElement).trigger('input');
|
||
restoreCaretPosition($(this).get(0), caretPosition);
|
||
return;
|
||
}
|
||
|
||
const masterMin = Number($(masterElement).attr('min'));
|
||
const masterMax = Number($(masterElement).attr('max'));
|
||
|
||
if (myValue < masterMin) {
|
||
console.warn('Label input is less than minimum.', myText, '<', masterMin);
|
||
restoreCaretPosition($(this).get(0), caretPosition);
|
||
return;
|
||
}
|
||
|
||
if (myValue > masterMax) {
|
||
console.warn('Label input is more than maximum.', myText, '>', masterMax);
|
||
restoreCaretPosition($(this).get(0), caretPosition);
|
||
return;
|
||
}
|
||
|
||
console.debug('Label value OK, setting to the master input control', myText);
|
||
$(masterElement).val(myValue).trigger('input').trigger('mouseup');
|
||
restoreCaretPosition($(this).get(0), caretPosition);
|
||
});
|
||
|
||
$(".user_stats_button").on('click', function () {
|
||
userStatsHandler();
|
||
});
|
||
|
||
$('#external_import_button').on('click', async () => {
|
||
const html = `<h3>Enter the URL of the content to import</h3>
|
||
Supported sources:<br>
|
||
<ul class="justifyLeft">
|
||
<li>Chub characters (direct link or id)<br>Example: <tt>Anonymous/example-character</tt></li>
|
||
<li>Chub lorebooks (direct link or id)<br>Example: <tt>lorebooks/bartleby/example-lorebook</tt></li>
|
||
<li>More coming soon...</li>
|
||
<ul>`
|
||
const input = await callPopup(html, 'input');
|
||
|
||
if (!input) {
|
||
console.debug('Custom content import cancelled');
|
||
return;
|
||
}
|
||
|
||
const url = input.trim();
|
||
console.debug('Custom content import started', url);
|
||
|
||
const request = await fetch('/api/content/import', {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({ url }),
|
||
});
|
||
|
||
if (!request.ok) {
|
||
toastr.info(request.statusText, 'Custom content import failed');
|
||
console.error('Custom content import failed', request.status, request.statusText);
|
||
return;
|
||
}
|
||
|
||
const data = await request.blob();
|
||
const customContentType = request.headers.get('X-Custom-Content-Type');
|
||
const fileName = request.headers.get('Content-Disposition').split('filename=')[1].replace(/"/g, '');
|
||
const file = new File([data], fileName, { type: data.type });
|
||
|
||
switch (customContentType) {
|
||
case 'character':
|
||
processDroppedFiles([file]);
|
||
break;
|
||
case 'lorebook':
|
||
await importWorldInfo(file);
|
||
break;
|
||
default:
|
||
toastr.warning('Unknown content type');
|
||
console.error('Unknown content type', customContentType);
|
||
break;
|
||
}
|
||
});
|
||
|
||
/**
|
||
* Handles the click event for the third-party extension import button.
|
||
* Prompts the user to enter the Git URL of the extension to import.
|
||
* After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension.
|
||
* If the extension is imported successfully, a success message is displayed.
|
||
* If the extension import fails, an error message is displayed and the error is logged to the console.
|
||
* After successfully importing the extension, the extension settings are reloaded and a 'EXTENSION_SETTINGS_LOADED' event is emitted.
|
||
*
|
||
* @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element.
|
||
*/
|
||
$('#third_party_extension_button').on('click', async () => {
|
||
const html = `<h3>Enter the Git URL of the extension to import</h3>
|
||
<br>
|
||
<p><b>Disclaimer:</b> Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.</p>
|
||
<br>
|
||
<p>Example: <tt> https://github.com/author/extension-name </tt></p>`
|
||
const input = await callPopup(html, 'input');
|
||
|
||
if (!input) {
|
||
console.debug('Extension import cancelled');
|
||
return;
|
||
}
|
||
|
||
const url = input.trim();
|
||
console.debug('Extension import started', url);
|
||
|
||
const request = await fetch('/api/extensions/install', {
|
||
method: 'POST',
|
||
headers: getRequestHeaders(),
|
||
body: JSON.stringify({ url }),
|
||
});
|
||
|
||
if (!request.ok) {
|
||
toastr.info(request.statusText, 'Extension import failed');
|
||
console.error('Extension import failed', request.status, request.statusText);
|
||
return;
|
||
}
|
||
|
||
const response = await request.json();
|
||
toastr.success(`Extension "${response.display_name}" by ${response.author} (version ${response.version}) has been imported successfully!`, 'Extension import successful');
|
||
console.debug(`Extension "${response.display_name}" has been imported successfully at ${response.extensionPath}`);
|
||
await loadExtensionSettings(settings);
|
||
eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
|
||
});
|
||
|
||
|
||
const $dropzone = $(document.body);
|
||
|
||
$dropzone.on('dragover', (event) => {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
$dropzone.addClass('dragover');
|
||
});
|
||
|
||
$dropzone.on('dragleave', (event) => {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
$dropzone.removeClass('dragover');
|
||
});
|
||
|
||
$dropzone.on('drop', async (event) => {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
$dropzone.removeClass('dragover');
|
||
|
||
const files = Array.from(event.originalEvent.dataTransfer.files);
|
||
if (!files.length) {
|
||
await importFromURL(event.originalEvent.dataTransfer.items, files);
|
||
}
|
||
processDroppedFiles(files);
|
||
});
|
||
|
||
|
||
$("#charListGridToggle").on('click', async () => {
|
||
doCharListDisplaySwitch();
|
||
});
|
||
|
||
$("#hideCharPanelAvatarButton").on('click', () => {
|
||
$('#avatar-and-name-block').slideToggle()
|
||
});
|
||
|
||
$(document).on('mouseup touchend', '#show_more_messages', () => {
|
||
showMoreMessages();
|
||
});
|
||
|
||
// Added here to prevent execution before script.js is loaded and get rid of quirky timeouts
|
||
await firstLoadInit();
|
||
|
||
registerDebugFunction('backfillTokenCounts', 'Backfill token counters',
|
||
`Recalculates token counts of all messages in the current chat to refresh the counters.
|
||
Useful when you switch between models that have different tokenizers.
|
||
This is a visual change only. Your chat will be reloaded.`, async () => {
|
||
for (const message of chat) {
|
||
// System messages are not counted
|
||
if (message.is_system) {
|
||
continue;
|
||
}
|
||
|
||
if (!message.extra) {
|
||
message.extra = {};
|
||
}
|
||
|
||
message.extra.token_count = getTokenCount(message.mes, 0);
|
||
}
|
||
|
||
await saveChatConditional();
|
||
await reloadCurrentChat();
|
||
});
|
||
});
|