import { encode } from "../scripts/gpt-2-3-tokenizer/mod.js"; import { Generate, /* getSettings, saveSettings, printMessages, clearChat, getChat, chat settings, */ this_chid, characters, online_status, main_api, api_server, api_key_novel, is_send_press, } from "../script.js"; import { LoadLocal, SaveLocal, ClearLocal, CheckLocal, LoadLocalBool } from "./f-localStorage.js"; import { selected_group, is_group_generating } from "./group-chats.js"; var NavToggle = document.getElementById("nav-toggle"); var PanelPin = document.getElementById("rm_button_panel_pin"); var SelectedCharacterTab = document.getElementById("rm_button_selected_ch"); var RightNavPanel = document.getElementById("right-nav-panel"); var AdvancedCharDefsPopup = document.getElementById("character_popup"); var ConfirmationPopup = document.getElementById("dialogue_popup"); var AutoConnectCheckbox = document.getElementById("auto-connect-checkbox"); var AutoLoadChatCheckbox = document.getElementById("auto-load-chat-checkbox"); var SelectedNavTab = ("#"+LoadLocal('SelectedNavTab')); var create_save_name; var create_save_description; var create_save_personality; var create_save_first_message; var create_save_scenario; var create_save_mes_example; var count_tokens; var perm_tokens; const observerConfig = { childList: true, subtree: true }; const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.target.id === "online_status_text2") { RA_checkOnlineStatus(); } else if (mutation.target.parentNode === SelectedCharacterTab) { setTimeout(RA_CountCharTokens, 200); } }); }); observer.observe(document.documentElement, observerConfig); //RossAscends: Added function to format dates used in files and chat timestamps to a humanized format. //Mostly I wanted this to be for file names, but couldn't figure out exactly where the filename save code was as everything seemed to be connected. //Does not break old characters/chats, as the code just uses whatever timestamp exists in the chat. //New chats made with characters will use this new formatting. //Useable variable is (( HumanizedDateTime )) export function humanizedDateTime() { let baseDate = new Date(Date.now()); let humanYear = baseDate.getFullYear(); let humanMonth = baseDate.getMonth() + 1; let humanDate = baseDate.getDate(); let humanHour = (baseDate.getHours() < 10 ? "0" : "") + baseDate.getHours(); let humanMinute = (baseDate.getMinutes() < 10 ? "0" : "") + baseDate.getMinutes(); let humanSecond = (baseDate.getSeconds() < 10 ? "0" : "") + baseDate.getSeconds(); let humanMillisecond = (baseDate.getMilliseconds() < 10 ? "0" : "") + baseDate.getMilliseconds(); let HumanizedDateTime = humanYear +"-" +humanMonth +"-" +humanDate +" @" +humanHour +"h " +humanMinute +"m " +humanSecond +"s " +humanMillisecond +"ms"; return HumanizedDateTime; } // triggers: $("#rm_button_create").on("click", function () { //when "+New Character" is clicked $(SelectedCharacterTab).children("h2").html(''); // empty nav's 3rd panel tab //empty temp vars to store new char data for counting create_save_name=""; create_save_description=""; create_save_personality=""; create_save_first_message=""; create_save_scenario=""; create_save_mes_example=""; $("#result_info").html('Type to start counting tokens!'); }); $("#rm_ch_create_block").on("input", function () {RA_CountCharTokens();}); //when any input is made to the create/edit character form textareas $("#character_popup").on("input", function () {RA_CountCharTokens();}); //when any input is made to the advanced editing popup textareas //function: function RA_CountCharTokens() { $("#result_info").html(""); console.log('RA_TC -- starting with this_chid = '+this_chid); if (document.getElementById('name_div').style.display == "block"){ //if new char $("#form_create").on("input", function () { //fill temp vars with form_create values create_save_name = $("#character_name_pole").val(); create_save_description = $("#description_textarea").val(); create_save_first_message = $("#firstmessage_textarea").val(); }); $("#character_popup").on("input", function () { //fill temp vars with advanced popup values create_save_personality = $("#personality_textarea").val(); create_save_scenario = $("#scenario_pole").val(); create_save_mes_example = $("#mes_example_textarea").val(); }); //count total tokens, including those that will be removed from context once chat history is long count_tokens = encode(JSON.stringify( create_save_name + create_save_description + create_save_personality + create_save_scenario + create_save_first_message + create_save_mes_example )).length; //count permanent tokens that will never get flushed out of context perm_tokens = encode(JSON.stringify( create_save_name + create_save_description + create_save_personality + create_save_scenario )).length; } else {if (this_chid !== undefined && this_chid !== "invalid-safety-id") { // if we are counting a valid pre-saved char //same as above, all tokens including temporary ones count_tokens = encode( JSON.stringify( characters[this_chid].description + characters[this_chid].personality + characters[this_chid].scenario + characters[this_chid].first_mes + characters[this_chid].mes_example )).length; //permanent tokens count perm_tokens = encode( JSON.stringify( characters[this_chid].name + characters[this_chid].description + characters[this_chid].personality + characters[this_chid].scenario )).length; } else {console.log("RA_TC -- no valid char found, closing.");} // if neither, probably safety char or some error in loading } // display the counted tokens if (count_tokens < 1024 && perm_tokens < 1024) { $("#result_info").html(count_tokens + " Tokens (" + perm_tokens + " Permanent Tokens)"); //display normal if both counts are under 1024 } else {$("#result_info").html("" +count_tokens +" Tokens (" +perm_tokens +" Permanent Tokens)(TOO MANY)");} //warn if either are over 1024 } //Auto Load Last Charcter -- (fires when active_character is defined and auto_load_chat is true) async function RA_autoloadchat() { if (document.getElementById('CharID0') !== null){ console.log('char list loaded! clicking activeChar') var CharToAutoLoad = document.getElementById('CharID'+LoadLocal('ActiveChar')); if (CharToAutoLoad !=null){ CharToAutoLoad.click(); }else{ console.log(CharToAutoLoad + ' ActiveChar local var - not found: '+LoadLocal('ActiveChar')); } RestoreNavTab(); }else{ console.log('no char list yet..') setTimeout(RA_autoloadchat,100) // if the charcter list hadn't been loaded yet, try again. } } //only triggers when AutoLoadChat is enabled, consider adding this as an independent feature later. function RestoreNavTab(){ if($(rm_button_selected_ch).children("h2").text()!==''){ //check for a change in the character edit tab name console.log('detected ALC char finished loaded, proceeding to restore tab.'); $(SelectedNavTab).click() //click to restore saved tab when name has changed (signalling char load is done) }else{ setTimeout(RestoreNavTab,100) //if not changed yet, check again after 100ms } } //changes input bar and send button display depending on connection status function RA_checkOnlineStatus() { if (online_status == "no_connection") { $("#send_textarea").attr("placeholder", "Not connected to API!"); //Input bar placeholder tells users they are not connected $("#send_form").css("background-color", "rgba(100,0,0,0.7)"); //entire input form area is red when not connected $("#send_but").css("display", "none"); //send button is hidden when not connected; } else { if (online_status !== undefined && online_status !== "no_connection") { $("#send_textarea").attr("placeholder", "Type a message..."); //on connect, placeholder tells user to type message $("#send_form").css("background-color", "rgba(0,0,0,0.7)"); //on connect, form BG changes to transprent black if (!is_send_press && !(selected_group && is_group_generating)) { $("#send_but").css("display", "inline"); //on connect, send button shows } } } } //Auto-connect to API (when set to kobold, API URL exists, and auto_connect is true) function RA_autoconnect() { if (typeof online_status !== 'undefined' && (api_server !== '' || api_key_novel !== '')) { if (online_status === "no_connection" && LoadLocalBool('AutoConnectEnabled')) { if (isUrlOrAPIKey(api_server) && main_api === "kobold") { $("#api_url_text").val(api_server); $("#api_button").click(); //} else if (isUrlOrAPIKey(api_key_novel) && main_api === "novel") { // $("#api_key_novel").val(api_key_novel); // $("#api_button").click(); } } } else { setTimeout(RA_autoconnect, 100); } } function isUrlOrAPIKey(string) { //const pattern = /^\d{3}-\d{3}-\d{3}-\d{3}$/; //need a sample novelAI key to set this format try { new URL(string); return true; } catch (_) { // return pattern.test(string); } } $("document").ready(function () { // read the state of Nav Lock and whether the nav was open or not before page load. $(PanelPin).prop('checked', LoadLocalBool("NavLockOn")); if (LoadLocalBool("NavLockOn") == true){$(NavToggle).prop("checked", LoadLocalBool("NavOpened"));} // read the state of AutoConnect and AutoLoadChat. $(AutoConnectCheckbox).prop("checked",LoadLocalBool("AutoConnectEnabled")); $(AutoLoadChatCheckbox).prop("checked",LoadLocalBool("AutoLoadChatEnabled")); if (LoadLocalBool('AutoLoadChatEnabled') == true) {RA_autoloadchat();} //Autoconnect on page load if enabled, or when api type is changed if (LoadLocalBool("AutoConnectEnabled") == true) {RA_autoconnect()} $("#main_api").change(function () {RA_autoconnect();}); $("#api_button").click(function () {setTimeout(RA_checkOnlineStatus, 100);}); //close the RightNav panel when user clicks outside of it or related panels (adv editing popup, or dialog popups) $("html").click(function (e) { if ($(NavToggle).prop("checked") && !$(PanelPin).prop("checked") && $(e.target).attr("id") !== "nav-toggle" && !RightNavPanel.contains(e.target) && !AdvancedCharDefsPopup.contains(e.target) && !ConfirmationPopup.contains(e.target)) { NavToggle.click(); } }); //save NavLock prefs and record state of the Nav being open or closed $(NavToggle).on("change", function () {SaveLocal("NavOpened", $(NavToggle).prop("checked")); }); $(PanelPin).on("change",function () {SaveLocal("NavLockOn", $(PanelPin).prop("checked")); }); //save AutoConnect and AutoLoadChat prefs $(AutoConnectCheckbox).on("change",function () {SaveLocal("AutoConnectEnabled", $(AutoConnectCheckbox).prop("checked")); }); $(AutoLoadChatCheckbox).on("change",function () {SaveLocal("AutoLoadChatEnabled", $(AutoLoadChatCheckbox).prop("checked")); }); $("#rm_button_extensions").click(function() { SaveLocal('SelectedNavTab','rm_button_extensions'); }); $("#rm_button_settings").click( function (){SaveLocal('SelectedNavTab','rm_button_settings'); }); $(SelectedCharacterTab).click(function () {SaveLocal('SelectedNavTab','rm_button_selected_ch'); }); $("#rm_button_characters").click( function () { //if char list is clicked, in addition to saving it... SaveLocal('SelectedNavTab','rm_button_characters'); characters.sort(Intl.Collator().compare); // we sort the list }); // when a char is selected from the list, save them as the auto-load character for next page load $(document).on("click", ".character_select",function () {SaveLocal('ActiveChar',$(this).attr('chid')); }); //this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height) $('#send_textarea').on('input', function () { this.style.height = '40px'; this.style.height = (this.scrollHeight) + 'px'; }); //Regenerate if user swipes on the last mesage in chat document.addEventListener('swiped-left', function(e) { var SwipeButR = $('.swipe_right:last'); var SwipeTargetMesClassParent = e.target.closest('.last_mes'); if (SwipeTargetMesClassParent !== null){ if(SwipeButR.attr('style')=='display: flex;' == true){ SwipeButR.click(); } } }); document.addEventListener('swiped-right', function(e) { var SwipeButL = $('.swipe_left:last'); var SwipeTargetMesClassParent = e.target.closest('.last_mes'); if (SwipeTargetMesClassParent !== null){ if(SwipeButL.attr('style')=='display: flex;' == true){ SwipeButL.click(); } } }); //Additional hotkeys CTRL+ENTER and CTRL+UPARROW document.addEventListener("keydown", (event) => { if (event.ctrlKey && event.key == "Enter") { // Ctrl+Enter for Regeneration Last Response if (is_send_press == false) { Generate("regenerate"); } } if (event.ctrlKey && event.key == "ArrowUp") { //Ctrl+UpArrow for Connect to last server if (online_status === "no_connection") { document.getElementById("api_button").click(); } } if (event.ctrlKey && event.key == "ArrowLeft") { //for debug, show all local stored vars CheckLocal(); } if (event.ctrlKey && event.key == "ArrowRight") { //for debug, empty local storage state ClearLocal(); } if (event.key == "ArrowLeft") { //swipes left $('.swipe_left:last').click(); } if (event.key == "ArrowRight") { //swipes right $('.swipe_right:last').click(); } }); })