diff --git a/public/index.html b/public/index.html
index 105457fa6..890be2906 100644
--- a/public/index.html
+++ b/public/index.html
@@ -83,6 +83,7 @@
+
SillyTavern
@@ -1766,8 +1767,8 @@
-
+
diff --git a/public/script.js b/public/script.js
index edda1c376..7a23fc25a 100644
--- a/public/script.js
+++ b/public/script.js
@@ -7535,7 +7535,6 @@ jQuery(async function () {
///////////////////////////////////////////////////////////////////////////////////
$("#api_button").click(function (e) {
- e.stopPropagation();
if ($("#api_url_text").val() != "") {
let value = formatKoboldUrl(String($("#api_url_text").val()).trim());
@@ -7568,7 +7567,6 @@ jQuery(async function () {
});
$("#api_button_textgenerationwebui").click(async function (e) {
- e.stopPropagation();
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);
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index 99ffdfc13..0bbd92665 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -31,7 +31,7 @@ import {
SECRET_KEYS,
secret_state,
} from "./secrets.js";
-import { debounce, delay, getStringHash, waitUntilCondition } from "./utils.js";
+import { debounce, delay, getStringHash, isUrlOrAPIKey, waitUntilCondition } from "./utils.js";
import { chat_completion_sources, oai_settings } from "./openai.js";
import { getTokenCount } from "./tokenizers.js";
@@ -403,15 +403,6 @@ function RA_autoconnect(PrevApi) {
}
}
-function isUrlOrAPIKey(string) {
- try {
- new URL(string);
- return true;
- } catch (_) {
- // return pattern.test(string);
- }
-}
-
function OpenNavPanels() {
const deviceInfo = getDeviceInfo();
if (deviceInfo && deviceInfo.device.type === 'desktop') {
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index aa4edc312..d6c27f546 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -201,6 +201,7 @@ let power_user = {
fuzzy_search: false,
encode_tags: false,
lazy_load: 0,
+ servers: [],
};
let themes = [];
diff --git a/public/scripts/server-history.js b/public/scripts/server-history.js
new file mode 100644
index 000000000..03d1a8fcf
--- /dev/null
+++ b/public/scripts/server-history.js
@@ -0,0 +1,86 @@
+import { saveSettingsDebounced } from "../script.js";
+import { power_user } from "./power-user.js";
+import { isUrlOrAPIKey } from "./utils.js";
+
+/**
+ * @param {{ term: string; }} request
+ * @param {function} resolve
+ * @param {string} serverLabel
+ */
+function findServers(request, resolve, serverLabel) {
+ if (!power_user.servers) {
+ power_user.servers = [];
+ }
+
+ const needle = request.term.toLowerCase();
+ const result = power_user.servers.filter(x => x.label == serverLabel).sort((a, b) => b.lastConnection - a.lastConnection).map(x => x.url).slice(0, 5);
+ const hasExactMatch = result.findIndex(x => x.toLowerCase() == needle) !== -1;
+
+ if (request.term && !hasExactMatch) {
+ result.unshift(request.term);
+ }
+
+ resolve(result);
+}
+
+function selectServer(event, ui, serverLabel) {
+ // unfocus the input
+ $(event.target).val(ui.item.value).trigger('blur');
+
+ $('[data-server-connect]').each(function () {
+ const serverLabels = String($(this).data('server-connect')).split(',');
+
+ if (serverLabels.includes(serverLabel)) {
+ $(this).trigger('click');
+ }
+ });
+}
+
+function createServerAutocomplete() {
+ const inputElement = $(this);
+ const serverLabel = inputElement.data('server-history');
+
+ inputElement
+ .autocomplete({
+ source: (i, o) => findServers(i, o, serverLabel),
+ select: (e, u) => selectServer(e, u, serverLabel),
+ minLength: 0,
+ })
+ .focus(onInputFocus); // <== show tag list on click
+}
+
+function onInputFocus() {
+ $(this).autocomplete('search', $(this).val());
+}
+
+function onServerConnectClick() {
+ const serverLabels = String($(this).data('server-connect')).split(',');
+
+ serverLabels.forEach(serverLabel => {
+ if (!power_user.servers) {
+ power_user.servers = [];
+ }
+
+ const value = String($(`[data-server-history="${serverLabel}"]`).val()).toLowerCase().trim();
+
+ // Don't save empty values or invalid URLs
+ if (!value || !isUrlOrAPIKey(value)) {
+ return;
+ }
+
+ const server = power_user.servers.find(x => x.url === value && x.label === serverLabel);
+
+ if (!server) {
+ power_user.servers.push({ label: serverLabel, url: value, lastConnection: Date.now() });
+ } else {
+ server.lastConnection = Date.now();
+ }
+
+ saveSettingsDebounced();
+ });
+}
+
+jQuery(function () {
+ $('[data-server-history]').each(createServerAutocomplete);
+ $(document).on('click', '[data-server-connect]', onServerConnectClick);
+});
diff --git a/public/scripts/utils.js b/public/scripts/utils.js
index da011a00a..121c97334 100644
--- a/public/scripts/utils.js
+++ b/public/scripts/utils.js
@@ -18,6 +18,15 @@ export function escapeHtml(str) {
return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
}
+export function isUrlOrAPIKey(value) {
+ try {
+ new URL(value);
+ return true;
+ } catch (_) {
+ return false;
+ }
+}
+
/**
* Determines if a value is unique in an array.
* @param {any} value Current value.
@@ -850,7 +859,7 @@ export async function saveBase64AsFile(base64Data, characterName, filename = "",
/**
* Loads either a CSS or JS file and appends it to the appropriate document section.
- *
+ *
* @param {string} url - The URL of the file to be loaded.
* @param {string} type - The type of file to load: "css" or "js".
* @returns {Promise} - Resolves when the file has loaded, rejects if there's an error or invalid type.