mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into vectors
This commit is contained in:
6
public/context/OldDefault.json
Normal file
6
public/context/OldDefault.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Circumstances and context of the dialogue: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||||
|
"chat_start": "\nThen the roleplay chat between {{user}} and {{char}} begins.\n",
|
||||||
|
"example_separator": "This is how {{char}} should talk",
|
||||||
|
"name": "OldDefault"
|
||||||
|
}
|
@@ -83,6 +83,7 @@
|
|||||||
<script type="module" src="scripts/preset-manager.js"></script>
|
<script type="module" src="scripts/preset-manager.js"></script>
|
||||||
<script type="module" src="scripts/filters.js"></script>
|
<script type="module" src="scripts/filters.js"></script>
|
||||||
<script type="module" src="scripts/personas.js"></script>
|
<script type="module" src="scripts/personas.js"></script>
|
||||||
|
<script type="module" src="scripts/server-history.js"></script>
|
||||||
|
|
||||||
<title>SillyTavern</title>
|
<title>SillyTavern</title>
|
||||||
</head>
|
</head>
|
||||||
@@ -1766,8 +1767,8 @@
|
|||||||
<div id="kobold_api_block">
|
<div id="kobold_api_block">
|
||||||
<h4 data-i18n="API url">API url</h4>
|
<h4 data-i18n="API url">API url</h4>
|
||||||
<small data-i18n="Example: http://127.0.0.1:5000/api ">Example: http://127.0.0.1:5000/api </small>
|
<small data-i18n="Example: http://127.0.0.1:5000/api ">Example: http://127.0.0.1:5000/api </small>
|
||||||
<input id="api_url_text" name="api_url" class="text_pole" placeholder="http://127.0.0.1:5000/api" maxlength="500" value="" autocomplete="off">
|
<input id="api_url_text" name="api_url" class="text_pole" placeholder="http://127.0.0.1:5000/api" maxlength="500" value="" autocomplete="off" data-server-history="kobold">
|
||||||
<div id="api_button" class="menu_button" type="submit" data-i18n="Connect">Connect</div>
|
<div id="api_button" class="menu_button" type="submit" data-i18n="Connect" data-server-connect="kobold">Connect</div>
|
||||||
<div id="api_loading" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
|
<div id="api_loading" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="online_status2">
|
<div id="online_status2">
|
||||||
@@ -1857,15 +1858,15 @@
|
|||||||
<div class="flex1">
|
<div class="flex1">
|
||||||
<h4 data-i18n="Blocking API url">Blocking API url</h4>
|
<h4 data-i18n="Blocking API url">Blocking API url</h4>
|
||||||
<small data-i18n="Example: http://127.0.0.1:5000/api ">Example: http://127.0.0.1:5000/api </small>
|
<small data-i18n="Example: http://127.0.0.1:5000/api ">Example: http://127.0.0.1:5000/api </small>
|
||||||
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
|
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" data-server-history="ooba_blocking">
|
||||||
</div>
|
</div>
|
||||||
<div class="flex1">
|
<div class="flex1">
|
||||||
<h4 data-i18n="Streaming API url">Streaming API url</h4>
|
<h4 data-i18n="Streaming API url">Streaming API url</h4>
|
||||||
<small data-i18n="Example: ws://127.0.0.1:5005/api/v1/stream">Example: ws://127.0.0.1:5005/api/v1/stream </small>
|
<small data-i18n="Example: ws://127.0.0.1:5005/api/v1/stream">Example: ws://127.0.0.1:5005/api/v1/stream </small>
|
||||||
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
|
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off" data-server-history="ooba_streaming">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="api_button_textgenerationwebui" class="menu_button" type="submit" data-i18n="Connect">Connect</div>
|
<div id="api_button_textgenerationwebui" class="menu_button" type="submit" data-i18n="Connect" data-server-connect="ooba_blocking,ooba_streaming">Connect</div>
|
||||||
<div id="api_loading_textgenerationwebui" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
|
<div id="api_loading_textgenerationwebui" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@@ -2438,7 +2438,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||||||
mesExamples = formatInstructModeExamples(mesExamples, name1, name2)
|
mesExamples = formatInstructModeExamples(mesExamples, name1, name2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const exampleSeparator = power_user.context.example_separator ? `${power_user.context.example_separator}\n` : '';
|
const exampleSeparator = power_user.context.example_separator ? `${substituteParams(power_user.context.example_separator)}\n` : '';
|
||||||
const blockHeading = main_api === 'openai' ? '<START>\n' : exampleSeparator;
|
const blockHeading = main_api === 'openai' ? '<START>\n' : exampleSeparator;
|
||||||
let mesExamplesArray = mesExamples.split(/<START>/gi).slice(1).map(block => `${blockHeading}${block.trim()}\n`);
|
let mesExamplesArray = mesExamples.split(/<START>/gi).slice(1).map(block => `${blockHeading}${block.trim()}\n`);
|
||||||
|
|
||||||
@@ -3420,7 +3420,7 @@ function addChatsPreamble(mesSendString) {
|
|||||||
|
|
||||||
function addChatsSeparator(mesSendString) {
|
function addChatsSeparator(mesSendString) {
|
||||||
if (power_user.context.chat_start) {
|
if (power_user.context.chat_start) {
|
||||||
return power_user.context.chat_start + '\n' + mesSendString;
|
return substituteParams(power_user.context.chat_start) + '\n' + mesSendString;
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
@@ -7533,7 +7533,6 @@ jQuery(async function () {
|
|||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
$("#api_button").click(function (e) {
|
$("#api_button").click(function (e) {
|
||||||
e.stopPropagation();
|
|
||||||
if ($("#api_url_text").val() != "") {
|
if ($("#api_url_text").val() != "") {
|
||||||
let value = formatKoboldUrl(String($("#api_url_text").val()).trim());
|
let value = formatKoboldUrl(String($("#api_url_text").val()).trim());
|
||||||
|
|
||||||
@@ -7566,7 +7565,6 @@ jQuery(async function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("#api_button_textgenerationwebui").click(async function (e) {
|
$("#api_button_textgenerationwebui").click(async function (e) {
|
||||||
e.stopPropagation();
|
|
||||||
const url_source = api_use_mancer_webui ? "#mancer_api_url_text" : "#textgenerationwebui_api_url_text";
|
const url_source = api_use_mancer_webui ? "#mancer_api_url_text" : "#textgenerationwebui_api_url_text";
|
||||||
if ($(url_source).val() != "") {
|
if ($(url_source).val() != "") {
|
||||||
let value = formatTextGenURL(String($(url_source).val()).trim(), api_use_mancer_webui);
|
let value = formatTextGenURL(String($(url_source).val()).trim(), api_use_mancer_webui);
|
||||||
|
@@ -31,7 +31,7 @@ import {
|
|||||||
SECRET_KEYS,
|
SECRET_KEYS,
|
||||||
secret_state,
|
secret_state,
|
||||||
} from "./secrets.js";
|
} 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 { chat_completion_sources, oai_settings } from "./openai.js";
|
||||||
import { getTokenCount } from "./tokenizers.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() {
|
function OpenNavPanels() {
|
||||||
const deviceInfo = getDeviceInfo();
|
const deviceInfo = getDeviceInfo();
|
||||||
if (deviceInfo && deviceInfo.device.type === 'desktop') {
|
if (deviceInfo && deviceInfo.device.type === 'desktop') {
|
||||||
@@ -906,10 +897,13 @@ export function initRossMods() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$(document).on('keydown', function (event) {
|
$(document).on('keydown', function (event) {
|
||||||
processHotkeys(event);
|
processHotkeys(event.originalEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
|
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
|
||||||
|
/**
|
||||||
|
* @param {KeyboardEvent} event
|
||||||
|
*/
|
||||||
function processHotkeys(event) {
|
function processHotkeys(event) {
|
||||||
//Enter to send when send_textarea in focus
|
//Enter to send when send_textarea in focus
|
||||||
if ($(':focus').attr('id') === 'send_textarea') {
|
if ($(':focus').attr('id') === 'send_textarea') {
|
||||||
@@ -943,6 +937,14 @@ export function initRossMods() {
|
|||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alt+Enter or AltGr+Enter to Continue
|
||||||
|
if ((event.altKey || (event.altKey && event.ctrlKey)) && event.key == "Enter") {
|
||||||
|
if (is_send_press == false) {
|
||||||
|
console.debug("Continuing with Alt+Enter");
|
||||||
|
$('#option_continue').trigger('click');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead
|
// Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead
|
||||||
if (event.ctrlKey && event.key == "Enter") {
|
if (event.ctrlKey && event.key == "Enter") {
|
||||||
const editMesDone = $(".mes_edit_done:visible");
|
const editMesDone = $(".mes_edit_done:visible");
|
||||||
@@ -958,14 +960,6 @@ export function initRossMods() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alt+Enter to Continue
|
|
||||||
if (event.altKey && event.key == "Enter") {
|
|
||||||
if (is_send_press == false) {
|
|
||||||
console.debug("Continuing with Alt+Enter");
|
|
||||||
$('#option_continue').trigger('click');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to check if nanogallery2's lightbox is active
|
// Helper function to check if nanogallery2's lightbox is active
|
||||||
function isNanogallery2LightboxActive() {
|
function isNanogallery2LightboxActive() {
|
||||||
// Check if the body has the 'nGY2On' class, adjust this based on actual behavior
|
// Check if the body has the 'nGY2On' class, adjust this based on actual behavior
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { saveSettingsDebounced, callPopup, getRequestHeaders } from "../../../script.js";
|
import { saveSettingsDebounced, callPopup, getRequestHeaders, substituteParams } from "../../../script.js";
|
||||||
import { getContext, extension_settings } from "../../extensions.js";
|
import { getContext, extension_settings } from "../../extensions.js";
|
||||||
import { initScrollHeight, resetScrollHeight } from "../../utils.js";
|
import { initScrollHeight, resetScrollHeight } from "../../utils.js";
|
||||||
import { executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "../../slash-commands.js";
|
import { executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "../../slash-commands.js";
|
||||||
@@ -15,6 +15,8 @@ const defaultSettings = {
|
|||||||
quickReplyEnabled: false,
|
quickReplyEnabled: false,
|
||||||
numberOfSlots: 5,
|
numberOfSlots: 5,
|
||||||
quickReplySlots: [],
|
quickReplySlots: [],
|
||||||
|
placeBeforePromptEnabled: false,
|
||||||
|
quickActionEnabled: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
//method from worldinfo
|
//method from worldinfo
|
||||||
@@ -75,6 +77,8 @@ async function loadSettings(type) {
|
|||||||
|
|
||||||
$('#quickReplyEnabled').prop('checked', extension_settings.quickReply.quickReplyEnabled);
|
$('#quickReplyEnabled').prop('checked', extension_settings.quickReply.quickReplyEnabled);
|
||||||
$('#quickReplyNumberOfSlots').val(extension_settings.quickReply.numberOfSlots);
|
$('#quickReplyNumberOfSlots').val(extension_settings.quickReply.numberOfSlots);
|
||||||
|
$('#placeBeforePromptEnabled').prop('checked', extension_settings.quickReply.placeBeforePromptEnabled);
|
||||||
|
$('#quickActionEnabled').prop('checked', extension_settings.quickReply.quickActionEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onQuickReplyInput(id) {
|
function onQuickReplyInput(id) {
|
||||||
@@ -101,7 +105,12 @@ async function onQuickReplyEnabledInput() {
|
|||||||
|
|
||||||
// New function to handle input on quickActionEnabled
|
// New function to handle input on quickActionEnabled
|
||||||
async function onQuickActionEnabledInput() {
|
async function onQuickActionEnabledInput() {
|
||||||
extension_settings.quickReply.quickActionEnabled = $(this).prop('checked');
|
extension_settings.quickReply.quickActionEnabled = !!$(this).prop('checked');
|
||||||
|
saveSettingsDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onPlaceBeforePromptEnabledInput() {
|
||||||
|
extension_settings.quickReply.placeBeforePromptEnabled = !!$(this).prop('checked');
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,12 +127,18 @@ async function sendQuickReply(index) {
|
|||||||
|
|
||||||
if (existingText) {
|
if (existingText) {
|
||||||
// If existing text, add space after prompt
|
// If existing text, add space after prompt
|
||||||
newText = existingText + ' ' + prompt + ' ';
|
if (extension_settings.quickReply.placeBeforePromptEnabled) {
|
||||||
|
newText = `${prompt} ${existingText} `;
|
||||||
|
} else {
|
||||||
|
newText = `${existingText} ${prompt} `;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// If no existing text, add prompt only (with a trailing space)
|
// If no existing text, add prompt only (with a trailing space)
|
||||||
newText = prompt + ' ';
|
newText = prompt + ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newText = substituteParams(newText);
|
||||||
|
|
||||||
$("#send_textarea").val(newText);
|
$("#send_textarea").val(newText);
|
||||||
|
|
||||||
// Set the focus back to the textarea
|
// Set the focus back to the textarea
|
||||||
@@ -131,7 +146,7 @@ async function sendQuickReply(index) {
|
|||||||
|
|
||||||
// Only trigger send button if quickActionEnabled is not checked or
|
// Only trigger send button if quickActionEnabled is not checked or
|
||||||
// the prompt starts with '/'
|
// the prompt starts with '/'
|
||||||
if (!$("#quickActionEnabled").prop('checked') || prompt.startsWith('/')) {
|
if (!extension_settings.quickReply.quickActionEnabled || prompt.startsWith('/')) {
|
||||||
$("#send_but").trigger('click');
|
$("#send_but").trigger('click');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,6 +354,10 @@ jQuery(async () => {
|
|||||||
<input id="quickActionEnabled" type="checkbox" />
|
<input id="quickActionEnabled" type="checkbox" />
|
||||||
Disable Send / Insert In User Input
|
Disable Send / Insert In User Input
|
||||||
</label>
|
</label>
|
||||||
|
<label class="checkbox_label marginBot10 wide100p flexnowrap">
|
||||||
|
<input id="placeBeforePromptEnabled" type="checkbox" />
|
||||||
|
Place Quick-reply before the Prompt
|
||||||
|
</label>
|
||||||
<div class="flex-container flexnowrap wide100p">
|
<div class="flex-container flexnowrap wide100p">
|
||||||
<select id="quickReplyPresets" name="quickreply-preset">
|
<select id="quickReplyPresets" name="quickreply-preset">
|
||||||
</select>
|
</select>
|
||||||
@@ -363,6 +382,7 @@ jQuery(async () => {
|
|||||||
|
|
||||||
// Add event handler for quickActionEnabled
|
// Add event handler for quickActionEnabled
|
||||||
$('#quickActionEnabled').on('input', onQuickActionEnabledInput);
|
$('#quickActionEnabled').on('input', onQuickActionEnabledInput);
|
||||||
|
$('#placeBeforePromptEnabled').on('input', onPlaceBeforePromptEnabledInput);
|
||||||
$('#quickReplyEnabled').on('input', onQuickReplyEnabledInput);
|
$('#quickReplyEnabled').on('input', onQuickReplyEnabledInput);
|
||||||
$('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput);
|
$('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput);
|
||||||
$("#quickReplyPresetSaveButton").on('click', saveQuickReplyPreset);
|
$("#quickReplyPresetSaveButton").on('click', saveQuickReplyPreset);
|
||||||
|
@@ -201,6 +201,7 @@ let power_user = {
|
|||||||
fuzzy_search: false,
|
fuzzy_search: false,
|
||||||
encode_tags: false,
|
encode_tags: false,
|
||||||
lazy_load: 0,
|
lazy_load: 0,
|
||||||
|
servers: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
let themes = [];
|
let themes = [];
|
||||||
|
86
public/scripts/server-history.js
Normal file
86
public/scripts/server-history.js
Normal file
@@ -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);
|
||||||
|
});
|
@@ -18,6 +18,15 @@ export function escapeHtml(str) {
|
|||||||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
return String(str).replace(/&/g, '&').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.
|
* Determines if a value is unique in an array.
|
||||||
* @param {any} value Current value.
|
* @param {any} value Current value.
|
||||||
|
Reference in New Issue
Block a user