Merge branch 'staging' into no-char-mode

This commit is contained in:
Cohee
2024-09-02 01:07:36 +03:00
16 changed files with 2008 additions and 1928 deletions

View File

@@ -344,7 +344,6 @@
@media screen and (min-width: 1001px) {
#PygOverrides,
#ContextFormatting,
#UI-Theme-Block,
#UI-Customization,

View File

@@ -360,6 +360,10 @@
margin-right: 2px;
}
.flexAuto {
flex: auto;
}
.flex0 {
flex: 0;
}
@@ -490,7 +494,8 @@
}
input:disabled,
textarea:disabled {
textarea:disabled,
.disabled {
cursor: not-allowed;
filter: brightness(0.5);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods, shouldSendOnEnter } from './scripts/RossAscends-mods.js';
import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods, shouldSendOnEnter, addSafariPatch } from './scripts/RossAscends-mods.js';
import { userStatsHandler, statMesProcess, initStats } from './scripts/stats.js';
import {
generateKoboldWithStreaming,
@@ -78,8 +78,6 @@ import {
renderStoryString,
sortEntitiesList,
registerDebugFunction,
ui_mode,
switchSimpleMode,
flushEphemeralStoppingStrings,
context_presets,
resetMovableStyles,
@@ -454,7 +452,9 @@ export const event_types = {
// TODO: Naming convention is inconsistent with other events
CHARACTER_DELETED: 'characterDeleted',
CHARACTER_DUPLICATED: 'character_duplicated',
SMOOTH_STREAM_TOKEN_RECEIVED: 'smooth_stream_token_received',
/** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */
SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received',
STREAM_TOKEN_RECEIVED: 'stream_token_received',
FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',
WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',
OPEN_CHARACTER_LIBRARY: 'open_character_library',
@@ -928,6 +928,7 @@ async function firstLoadInit() {
throw new Error('Initialization failed');
}
addSafariPatch();
await getClientVersion();
await readSecretState();
initLocales();
@@ -3126,6 +3127,7 @@ class StreamingProcessor {
if (logprobs) {
this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs]));
}
await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));
}
const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
@@ -6324,15 +6326,11 @@ export function setUserName(value) {
}
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');
});
let userName = await callGenericPopup(template, POPUP_TYPE.INPUT, currentUser?.name || name1, { rows: 2, wide: true, large: true });
let userName = await callGenericPopup(template, POPUP_TYPE.INPUT, currentUser?.name || name1, { rows: 2, wider: true, cancelButton: false });
if (userName) {
userName = userName.replace('\n', ' ');
userName = String(userName).replace('\n', ' ');
setUserName(userName);
console.log(`Binding persona ${avatarId} to name ${userName}`);
power_user.personas[avatarId] = userName;
@@ -6341,12 +6339,6 @@ async function doOnboarding(avatarId) {
position: persona_description_positions.IN_PROMPT,
};
}
if (simpleUiMode) {
power_user.ui_mode = ui_mode.SIMPLE;
$('#ui_mode_select').val(power_user.ui_mode);
switchSimpleMode();
}
}
function reloadLoop() {
@@ -7466,6 +7458,53 @@ export function hideSwipeButtons() {
$('#chat').find('.swipe_left').css('display', 'none');
}
/**
* Deletes a swipe from the chat.
*
* @param {number?} swipeId - The ID of the swipe to delete. If not provided, the current swipe will be deleted.
* @returns {Promise<number>|undefined} - The ID of the new swipe after deletion.
*/
export async function deleteSwipe(swipeId = null) {
if (swipeId && (isNaN(swipeId) || swipeId < 0)) {
toastr.warning(`Invalid swipe ID: ${swipeId + 1}`);
return;
}
const lastMessage = chat[chat.length - 1];
if (!lastMessage || !Array.isArray(lastMessage.swipes) || !lastMessage.swipes.length) {
toastr.warning('No messages to delete swipes from.');
return;
}
if (lastMessage.swipes.length <= 1) {
toastr.warning('Can\'t delete the last swipe.');
return;
}
swipeId = swipeId ?? lastMessage.swipe_id;
if (swipeId < 0 || swipeId >= lastMessage.swipes.length) {
toastr.warning(`Invalid swipe ID: ${swipeId + 1}`);
return;
}
lastMessage.swipes.splice(swipeId, 1);
if (Array.isArray(lastMessage.swipe_info) && lastMessage.swipe_info.length) {
lastMessage.swipe_info.splice(swipeId, 1);
}
// Select the next swip, or the one before if it was the last one
const newSwipeId = Math.min(swipeId, lastMessage.swipes.length - 1);
lastMessage.swipe_id = newSwipeId;
lastMessage.mes = lastMessage.swipes[newSwipeId];
await saveChatConditional();
await reloadCurrentChat();
return newSwipeId;
}
export async function saveMetadata() {
if (selected_group) {
await editGroup(selected_group, true, false);
@@ -8009,7 +8048,7 @@ window['SillyTavern'].getContext = function () {
registerHelper: () => { },
registerMacro: MacrosParser.registerMacro.bind(MacrosParser),
unregisterMacro: MacrosParser.unregisterMacro.bind(MacrosParser),
registedDebugFunction: registerDebugFunction,
registerDebugFunction: registerDebugFunction,
/** @deprecated Use renderExtensionTemplateAsync instead. */
renderExtensionTemplate: renderExtensionTemplate,
renderExtensionTemplateAsync: renderExtensionTemplateAsync,
@@ -8944,6 +8983,12 @@ function addDebugFunctions() {
await reloadCurrentChat();
};
registerDebugFunction('forceOnboarding', 'Force onboarding', 'Forces the onboarding process to restart.', async () => {
firstRun = true;
await saveSettings();
location.reload();
});
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.
@@ -10153,7 +10198,7 @@ jQuery(async function () {
}
});
$(document).on('click', '.mes_edit_cancel', function () {
$(document).on('click', '.mes_edit_cancel', async function () {
let text = chat[this_edit_mes_id]['mes'];
$(this).closest('.mes_block').find('.mes_text').empty();
@@ -10171,6 +10216,8 @@ jQuery(async function () {
));
appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes'));
addCopyToCodeBlocks($(this).closest('.mes'));
await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
this_edit_mes_id = undefined;
});
@@ -10282,20 +10329,13 @@ jQuery(async function () {
if (deleteOnlySwipe) {
const message = chat[this_edit_mes_id];
const swipe_id = message.swipe_id;
message.swipes.splice(swipe_id, 1);
if (Array.isArray(message.swipe_info) && message.swipe_info.length) {
message.swipe_info.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);
messageElement.remove();
await deleteSwipe(swipe_id);
return;
}
chat.splice(this_edit_mes_id, 1);
messageElement.remove();
let startFromZero = Number(this_edit_mes_id) === 0;
this_edit_mes_id = undefined;

View File

@@ -296,7 +296,7 @@ export async function favsToHotswap() {
//helpful instruction message if no characters are favorited
if (favs.length == 0) {
container.html(`<small><span><i class="fa-solid fa-star"></i>${DOMPurify.sanitize(container.attr('no_favs'))}</span></small>`);
container.html(`<small><span><i class="fa-solid fa-star"></i>&nbsp;${DOMPurify.sanitize(container.attr('no_favs'))}</span></small>`);
return;
}
@@ -711,6 +711,18 @@ export const autoFitSendTextAreaDebounced = debounce(autoFitSendTextArea, deboun
// ---------------------------------------------------
export function addSafariPatch() {
const userAgent = getParsedUA();
console.debug('User Agent', userAgent);
const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
const isDesktopSafari = userAgent?.browser?.name === 'Safari' && userAgent?.platform?.type === 'desktop';
const isIOS = userAgent?.os?.name === 'iOS';
if (isIOS || isMobileSafari || isDesktopSafari) {
document.body.classList.add('safari');
}
}
export function initRossMods() {
// initial status check
setTimeout(() => {
@@ -725,16 +737,6 @@ export function initRossMods() {
RA_autoconnect();
}
const userAgent = getParsedUA();
console.debug('User Agent', userAgent);
const isMobileSafari = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
const isDesktopSafari = userAgent?.browser?.name === 'Safari' && userAgent?.platform?.type === 'desktop';
const isIOS = userAgent?.os?.name === 'iOS';
if (isIOS || isMobileSafari || isDesktopSafari) {
document.body.classList.add('safari');
}
$('#main_api').change(function () {
var PrevAPI = main_api;
setTimeout(() => RA_autoconnect(PrevAPI), 100);

View File

@@ -15,6 +15,7 @@ import {
generateRaw,
getMaxContextSize,
setExtensionPrompt,
streamingProcessor,
} from '../../../script.js';
import { is_group_generating, selected_group } from '../../group-chats.js';
import { loadMovingUIState } from '../../power-user.js';
@@ -408,8 +409,8 @@ async function onChatEvent() {
return;
}
// Generation is in progress, summary prevented
if (is_send_press) {
// Streaming in-progress
if (streamingProcessor && !streamingProcessor.isFinished) {
return;
}
@@ -446,15 +447,9 @@ async function onChatEvent() {
delete chat[chat.length - 1].extra.memory;
}
try {
await summarizeChat(context);
}
catch (error) {
console.log(error);
}
finally {
saveLastValues();
}
summarizeChat(context)
.catch(console.error)
.finally(saveLastValues);
}
/**
@@ -567,7 +562,7 @@ async function getSummaryPromptForNow(context, force) {
await waitUntilCondition(() => is_group_generating === false, 1000, 10);
}
// Wait for the send button to be released
waitUntilCondition(() => is_send_press === false, 30000, 100);
await waitUntilCondition(() => is_send_press === false, 30000, 100);
} catch {
console.debug('Timeout waiting for is_send_press');
return '';
@@ -650,19 +645,29 @@ async function summarizeChatWebLLM(context, force) {
params.max_tokens = extension_settings.memory.overrideResponseLength;
}
const summary = await generateWebLlmChatPrompt(messages, params);
const newContext = getContext();
try {
inApiCall = true;
const summary = await generateWebLlmChatPrompt(messages, params);
const newContext = getContext();
// something changed during summarization request
if (newContext.groupId !== context.groupId ||
newContext.chatId !== context.chatId ||
(!newContext.groupId && (newContext.characterId !== context.characterId))) {
console.log('Context changed, summary discarded');
return;
if (!summary) {
console.warn('Empty summary received');
return;
}
// something changed during summarization request
if (newContext.groupId !== context.groupId ||
newContext.chatId !== context.chatId ||
(!newContext.groupId && (newContext.characterId !== context.characterId))) {
console.log('Context changed, summary discarded');
return;
}
setMemoryContext(summary, true, lastUsedIndex);
return summary;
} finally {
inApiCall = false;
}
setMemoryContext(summary, true, lastUsedIndex);
return summary;
}
async function summarizeChatMain(context, force, skipWIAN) {
@@ -677,12 +682,18 @@ async function summarizeChatMain(context, force, skipWIAN) {
let index = null;
if (prompt_builders.DEFAULT === extension_settings.memory.prompt_builder) {
summary = await generateQuietPrompt(prompt, false, skipWIAN, '', '', extension_settings.memory.overrideResponseLength);
try {
inApiCall = true;
summary = await generateQuietPrompt(prompt, false, skipWIAN, '', '', extension_settings.memory.overrideResponseLength);
} finally {
inApiCall = false;
}
}
if ([prompt_builders.RAW_BLOCKING, prompt_builders.RAW_NON_BLOCKING].includes(extension_settings.memory.prompt_builder)) {
const lock = extension_settings.memory.prompt_builder === prompt_builders.RAW_BLOCKING;
try {
inApiCall = true;
if (lock) {
deactivateSendButtons();
}
@@ -700,12 +711,18 @@ async function summarizeChatMain(context, force, skipWIAN) {
summary = await generateRaw(rawPrompt, '', false, false, prompt, extension_settings.memory.overrideResponseLength);
index = lastUsedIndex;
} finally {
inApiCall = false;
if (lock) {
activateSendButtons();
}
}
}
if (!summary) {
console.warn('Empty summary received');
return;
}
const newContext = getContext();
// something changed during summarization request
@@ -840,6 +857,11 @@ async function summarizeChatExtras(context) {
const summary = await callExtrasSummarizeAPI(resultingString);
const newContext = getContext();
if (!summary) {
console.warn('Empty summary received');
return;
}
// something changed during summarization request
if (newContext.groupId !== context.groupId
|| newContext.chatId !== context.chatId

View File

@@ -7,7 +7,7 @@ import {
power_user,
context_presets,
} from './power-user.js';
import { regexFromString, resetScrollHeight } from './utils.js';
import { regexFromString } from './utils.js';
/**
* @type {any[]} Instruct mode presets.
@@ -78,28 +78,29 @@ function migrateInstructModeSettings(settings) {
* Loads instruct mode settings from the given data object.
* @param {object} data Settings data object.
*/
export function loadInstructMode(data) {
export async function loadInstructMode(data) {
if (data.instruct !== undefined) {
instruct_presets = data.instruct;
}
migrateInstructModeSettings(power_user.instruct);
$('#instruct_enabled').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.enabled);
$('#instructSettingsBlock, #InstructSequencesColumn').toggleClass('disabled', !power_user.instruct.enabled);
$('#instruct_bind_to_context').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.bind_to_context);
controls.forEach(control => {
const $element = $(`#${control.id}`);
if (control.isCheckbox) {
$element.prop('checked', power_user.instruct[control.property]);
} else {
$element.val(power_user.instruct[control.property]);
$element[0].innerText = (power_user.instruct[control.property]);
}
$element.on('input', async function () {
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this)[0].innerText;
saveSettingsDebounced();
if (!control.isCheckbox) {
await resetScrollHeight($element);
}
});
if (control.trigger) {
@@ -107,6 +108,9 @@ export function loadInstructMode(data) {
}
});
$('#instruct_system_sequence').css('height', 'auto');
$('#instruct_system_suffix').css('height', 'auto');
instruct_presets.forEach((preset) => {
const name = preset.name;
const option = document.createElement('option');
@@ -604,11 +608,29 @@ jQuery(() => {
$('#instruct_system_same_as_user').on('input', function () {
const state = !!$(this).prop('checked');
$('#instruct_system_sequence').prop('disabled', state);
$('#instruct_system_suffix').prop('disabled', state);
if (state) {
$('#instruct_system_sequence_block').addClass('disabled');
$('#instruct_system_suffix_block').addClass('disabled');
$('#instruct_system_sequence').css('height', 'auto');
$('#instruct_system_suffix').css('height', 'auto');
$('#instruct_system_sequence').prop('contenteditable', false);
$('#instruct_system_suffix').prop('contenteditable', false);
} else {
$('#instruct_system_sequence_block').removeClass('disabled');
$('#instruct_system_suffix_block').removeClass('disabled');
$('#instruct_system_sequence').css('height', '');
$('#instruct_system_suffix').css('height', '');
$('#instruct_system_sequence').prop('contenteditable', true);
$('#instruct_system_suffix').prop('contenteditable', true);
}
});
$('#instruct_enabled').on('change', function () {
//color toggle for the main switch
$('#instruct_enabled').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.enabled);
$('#instructSettingsBlock, #InstructSequencesColumn').toggleClass('disabled', !power_user.instruct.enabled);
if (!power_user.instruct.bind_to_context) {
return;
}
@@ -622,6 +644,10 @@ jQuery(() => {
}
});
$('#instruct_bind_to_context').on('change', function () {
$('#instruct_bind_to_context').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.bind_to_context);
});
$('#instruct_presets').on('change', function () {
const name = String($(this).find(':selected').val());
const preset = instruct_presets.find(x => x.name === name);
@@ -641,7 +667,8 @@ jQuery(() => {
if (control.isCheckbox) {
$element.prop('checked', power_user.instruct[control.property]).trigger('input');
} else {
$element.val(power_user.instruct[control.property]).trigger('input');
$element[0].innerText = (power_user.instruct[control.property]);
$element.trigger('input');
}
}
});

View File

@@ -281,6 +281,7 @@ export class Popup {
case POPUP_TYPE.INPUT: {
this.mainInput.style.display = 'block';
if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-save');
if (cancelButton === false) this.cancelButton.style.display = 'none';
break;
}
case POPUP_TYPE.DISPLAY: {

View File

@@ -75,11 +75,6 @@ const defaultStoryString = '{{#if system}}{{system}}\n{{/if}}{{#if description}}
const defaultExampleSeparator = '***';
const defaultChatStart = '***';
export const ui_mode = {
SIMPLE: 0,
POWER: 1,
};
const avatar_styles = {
ROUND: 0,
RECTANGULAR: 1,
@@ -132,7 +127,6 @@ let power_user = {
smooth_streaming: false,
smooth_streaming_speed: 50,
ui_mode: ui_mode.POWER,
fast_ui_mode: true,
avatar_style: avatar_styles.ROUND,
chat_display: chat_styles.DEFAULT,
@@ -331,12 +325,6 @@ const debug_functions = [];
const setHotswapsDebounced = debounce(favsToHotswap);
export function switchSimpleMode() {
$('[data-newbie-hidden]').each(function () {
$(this).toggleClass('displayNone', power_user.ui_mode === ui_mode.SIMPLE);
});
}
function playMessageSound() {
if (!power_user.play_message_sound) {
return;
@@ -1481,7 +1469,7 @@ async function loadPowerUserSettings(settings, data) {
$('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length);
$('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(', '));
$('#auto_swipe_blacklist_threshold').val(power_user.auto_swipe_blacklist_threshold);
$('#custom_stopping_strings').val(power_user.custom_stopping_strings);
$('#custom_stopping_strings').text(power_user.custom_stopping_strings);
$('#custom_stopping_strings_macro').prop('checked', power_user.custom_stopping_strings_macro);
$('#fuzzy_search_checkbox').prop('checked', power_user.fuzzy_search);
$('#persona_show_notifications').prop('checked', power_user.persona_show_notifications);
@@ -1511,7 +1499,7 @@ async function loadPowerUserSettings(settings, data) {
$('#waifuMode').prop('checked', power_user.waifuMode);
$('#movingUImode').prop('checked', power_user.movingUI);
$('#noShadowsmode').prop('checked', power_user.noShadows);
$('#start_reply_with').val(power_user.user_prompt_bias);
$('#start_reply_with').text(power_user.user_prompt_bias);
$('#chat-show-reply-prefix-checkbox').prop('checked', power_user.show_user_prompt_bias);
$('#auto_continue_enabled').prop('checked', power_user.auto_continue.enabled);
$('#auto_continue_allow_chat_completions').prop('checked', power_user.auto_continue.allow_chat_completions);
@@ -1586,7 +1574,6 @@ async function loadPowerUserSettings(settings, data) {
$('#bot-mes-blur-tint-color-picker').attr('color', power_user.bot_mes_blur_tint_color);
$('#shadow-color-picker').attr('color', power_user.shadow_color);
$('#border-color-picker').attr('color', power_user.border_color);
$('#ui_mode_select').val(power_user.ui_mode).find(`option[value="${power_user.ui_mode}"]`).attr('selected', true);
$('#reduced_motion').prop('checked', power_user.reduced_motion);
$('#auto-connect-checkbox').prop('checked', power_user.auto_connect);
$('#auto-load-chat-checkbox').prop('checked', power_user.auto_load_chat);
@@ -1613,14 +1600,13 @@ async function loadPowerUserSettings(settings, data) {
switchReducedMotion();
switchCompactInputArea();
reloadMarkdownProcessor(power_user.render_formulas);
loadInstructMode(data);
await loadInstructMode(data);
await loadContextSettings();
loadMaxContextUnlocked();
switchWaifuMode();
switchSpoilerMode();
loadMovingUIState();
loadCharListState();
switchSimpleMode();
}
async function loadCharListState() {
@@ -1761,13 +1747,14 @@ async function loadContextSettings() {
if (control.isCheckbox) {
$element.prop('checked', power_user.context[control.property]);
} else {
$element.val(power_user.context[control.property]);
$element[0].innerText = power_user.context[control.property];
}
console.log(`Setting ${$element.prop('id')} to ${power_user.context[control.property]}`);
// If the setting already exists, no need to duplicate it
// TODO: Maybe check the power_user object for the setting instead of a flag?
$element.on('input', async function () {
const value = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
$element.on('input keyup', async function () {
const value = control.isCheckbox ? !!$(this).prop('checked') : $(this)[0].innerText;
if (control.isGlobalSetting) {
power_user[control.property] = value;
} else {
@@ -1775,9 +1762,6 @@ async function loadContextSettings() {
}
saveSettingsDebounced();
if (!control.isCheckbox) {
await resetScrollHeight($element);
}
});
});
@@ -1791,7 +1775,7 @@ async function loadContextSettings() {
});
$('#context_presets').on('change', function () {
const name = String($(this).find(':selected').val());
const name = String($(this).find(':selected').text());
const preset = context_presets.find(x => x.name === name);
if (!preset) {
@@ -1816,9 +1800,8 @@ async function loadContextSettings() {
.prop('checked', control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property])
.trigger('input');
} else {
$element
.val(control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property])
.trigger('input');
$element[0].innerText = (control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property])
$element.trigger('input');
}
}
});
@@ -3120,7 +3103,7 @@ $(document).ready(() => {
});
$('#start_reply_with').on('input', function () {
power_user.user_prompt_bias = String($(this).val());
power_user.user_prompt_bias = String($(this)[0].innerText);
saveSettingsDebounced();
});
@@ -3652,7 +3635,7 @@ $(document).ready(() => {
});
$('#custom_stopping_strings').on('input', function () {
power_user.custom_stopping_strings = String($(this).val());
power_user.custom_stopping_strings = String($(this)[0].innerText).trim();
saveSettingsDebounced();
});
@@ -3686,13 +3669,6 @@ $(document).ready(() => {
showDebugMenu();
});
$('#ui_mode_select').on('change', function () {
const value = $(this).find(':selected').val();
power_user.ui_mode = Number(value);
switchSimpleMode();
saveSettingsDebounced();
});
$('#bogus_folders').on('input', function () {
power_user.bogus_folders = !!$(this).prop('checked');
printCharactersDebounced();

View File

@@ -11,6 +11,7 @@ import {
comment_avatar,
deactivateSendButtons,
default_avatar,
deleteSwipe,
eventSource,
event_types,
extension_prompt_roles,
@@ -2309,37 +2310,10 @@ async function addSwipeCallback(args, value) {
}
async function deleteSwipeCallback(_, arg) {
const lastMessage = chat[chat.length - 1];
// Take the provided argument. Null if none provided, which will target the current swipe.
const swipeId = arg && !isNaN(Number(arg)) ? (Number(arg) - 1) : null;
if (!lastMessage || !Array.isArray(lastMessage.swipes) || !lastMessage.swipes.length) {
toastr.warning('No messages to delete swipes from.');
return '';
}
if (lastMessage.swipes.length <= 1) {
toastr.warning('Can\'t delete the last swipe.');
return '';
}
const swipeId = arg && !isNaN(Number(arg)) ? (Number(arg) - 1) : lastMessage.swipe_id;
if (swipeId < 0 || swipeId >= lastMessage.swipes.length) {
toastr.warning(`Invalid swipe ID: ${swipeId + 1}`);
return '';
}
lastMessage.swipes.splice(swipeId, 1);
if (Array.isArray(lastMessage.swipe_info) && lastMessage.swipe_info.length) {
lastMessage.swipe_info.splice(swipeId, 1);
}
const newSwipeId = Math.min(swipeId, lastMessage.swipes.length - 1);
lastMessage.swipe_id = newSwipeId;
lastMessage.mes = lastMessage.swipes[newSwipeId];
await saveChatConditional();
await reloadCurrentChat();
const newSwipeId = await deleteSwipe(swipeId);
return String(newSwipeId);
}

View File

@@ -36,6 +36,8 @@ export const enumIcons = {
true: '✔️',
false: '❌',
null: '🚫',
undefined: '❓',
// Value types
boolean: '🔲',
@@ -230,4 +232,19 @@ export const commonEnumProviders = {
enumTypes.enum, '💉');
});
},
/**
* Gets somewhat recognizable STscript types.
*
* @returns {SlashCommandEnumValue[]}
*/
types: () => [
new SlashCommandEnumValue('string', null, enumTypes.type, enumIcons.string),
new SlashCommandEnumValue('number', null, enumTypes.type, enumIcons.number),
new SlashCommandEnumValue('boolean', null, enumTypes.type, enumIcons.boolean),
new SlashCommandEnumValue('array', null, enumTypes.type, enumIcons.array),
new SlashCommandEnumValue('object', null, enumTypes.type, enumIcons.dictionary),
new SlashCommandEnumValue('null', null, enumTypes.type, enumIcons.null),
new SlashCommandEnumValue('undefined', null, enumTypes.type, enumIcons.undefined),
],
};

View File

@@ -1,4 +1,5 @@
import { SlashCommandClosure } from './SlashCommandClosure.js';
import { convertValueType } from '../utils.js';
export class SlashCommandScope {
/**@type {string[]}*/ variableNames = [];
@@ -55,7 +56,7 @@ export class SlashCommandScope {
if (this.existsVariableInScope(key)) throw new SlashCommandScopeVariableExistsError(`Variable named "${key}" already exists.`);
this.variables[key] = value;
}
setVariable(key, value, index = null) {
setVariable(key, value, index = null, type = null) {
if (this.existsVariableInScope(key)) {
if (index !== null && index !== undefined) {
let v = this.variables[key];
@@ -63,13 +64,13 @@ export class SlashCommandScope {
v = JSON.parse(v);
const numIndex = Number(index);
if (Number.isNaN(numIndex)) {
v[index] = value;
v[index] = convertValueType(value, type);
} else {
v[numIndex] = value;
v[numIndex] = convertValueType(value, type);
}
v = JSON.stringify(v);
} catch {
v[index] = value;
v[index] = convertValueType(value, type);
}
this.variables[key] = v;
} else {
@@ -78,7 +79,7 @@ export class SlashCommandScope {
return value;
}
if (this.parent) {
return this.parent.setVariable(key, value, index);
return this.parent.setVariable(key, value, index, type);
}
throw new SlashCommandScopeVariableNotFoundError(`No such variable: "${key}"`);
}

View File

@@ -1,4 +1,3 @@
import { eventSource, event_types } from '../script.js';
import { power_user } from './power-user.js';
import { delay } from './utils.js';
@@ -268,7 +267,6 @@ export class SmoothEventSourceStream extends EventSourceStream {
hasFocus && await delay(getDelay(lastStr));
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify(parsed.data) }));
lastStr = parsed.chunk;
hasFocus && await eventSource.emit(event_types.SMOOTH_STREAM_TOKEN_RECEIVED, parsed.chunk);
}
} catch (error) {
console.debug('Smooth Streaming parsing error', error);

View File

@@ -4,6 +4,7 @@ import { isMobile } from './RossAscends-mods.js';
import { collapseNewlines } from './power-user.js';
import { debounce_timeout } from './constants.js';
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
/**
* Pagination status string template.
@@ -33,6 +34,74 @@ export function isValidUrl(value) {
}
}
/**
* Converts string to a value of a given type. Includes pythonista-friendly aliases.
* @param {string|SlashCommandClosure} value String value
* @param {string} type Type to convert to
* @returns {any} Converted value
*/
export function convertValueType(value, type) {
if (value instanceof SlashCommandClosure || typeof type !== 'string') {
return value;
}
switch (type.trim().toLowerCase()) {
case 'string':
case 'str':
return String(value);
case 'null':
return null;
case 'undefined':
case 'none':
return undefined;
case 'number':
return Number(value);
case 'int':
return parseInt(value, 10);
case 'float':
return parseFloat(value);
case 'boolean':
case 'bool':
return isTrueBoolean(value);
case 'list':
case 'array':
try {
const parsedArray = JSON.parse(value);
if (Array.isArray(parsedArray)) {
return parsedArray;
}
// The value is not an array
return [];
} catch {
return [];
}
case 'object':
case 'dict':
case 'dictionary':
try {
const parsedObject = JSON.parse(value);
if (typeof parsedObject === 'object') {
return parsedObject;
}
// The value is not an object
return {};
} catch {
return {};
}
default:
return value;
}
}
/**
* Parses ranges like 10-20 or 10.
* Range is inclusive. Start must be less than end.

View File

@@ -11,7 +11,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
import { isFalseBoolean } from './utils.js';
import { isFalseBoolean, convertValueType } from './utils.js';
/** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */
/** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */
@@ -57,12 +57,12 @@ function setLocalVariable(name, value, args = {}) {
if (localVariable === null) {
localVariable = {};
}
localVariable[args.index] = value;
localVariable[args.index] = convertValueType(value, args.as);
} else {
if (localVariable === null) {
localVariable = [];
}
localVariable[numIndex] = value;
localVariable[numIndex] = convertValueType(value, args.as);
}
chat_metadata.variables[name] = JSON.stringify(localVariable);
} catch {
@@ -106,12 +106,12 @@ function setGlobalVariable(name, value, args = {}) {
if (globalVariable === null) {
globalVariable = {};
}
globalVariable[args.index] = value;
globalVariable[args.index] = convertValueType(value, args.as);
} else {
if (globalVariable === null) {
globalVariable = [];
}
globalVariable[numIndex] = value;
globalVariable[numIndex] = convertValueType(value, args.as);
}
extension_settings.variables.global[name] = JSON.stringify(globalVariable);
} catch {
@@ -667,23 +667,28 @@ function parseNumericSeries(value, scope = null) {
}
function performOperation(value, operation, singleOperand = false, scope = null) {
if (!value) {
return 0;
function getResult() {
if (!value) {
return 0;
}
const array = parseNumericSeries(value, scope);
if (array.length === 0) {
return 0;
}
const result = singleOperand ? operation(array[0]) : operation(array);
if (isNaN(result) || !isFinite(result)) {
return 0;
}
return result;
}
const array = parseNumericSeries(value, scope);
if (array.length === 0) {
return 0;
}
const result = singleOperand ? operation(array[0]) : operation(array);
if (isNaN(result) || !isFinite(result)) {
return 0;
}
return result;
const result = getResult();
return String(result);
}
function addValuesCallback(args, value) {
@@ -836,7 +841,7 @@ function varCallback(args, value) {
if (typeof key != 'string') throw new Error('Key must be a string');
if (args._hasUnnamedArgument) {
const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
args._scope.setVariable(key, val, args.index);
args._scope.setVariable(key, val, args.index, args.as);
return val;
} else {
return args._scope.getVariable(key, args.index);
@@ -846,7 +851,7 @@ function varCallback(args, value) {
if (typeof key != 'string') throw new Error('Key must be a string');
if (value.length > 0) {
const val = typeof value[0] == 'string' ? value.join(' ') : value[0];
args._scope.setVariable(key, val, args.index);
args._scope.setVariable(key, val, args.index, args.as);
return val;
} else {
return args._scope.getVariable(key, args.index);
@@ -901,6 +906,14 @@ export function registerVariableCommands() {
new SlashCommandNamedArgument(
'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false,
),
SlashCommandNamedArgument.fromProps({
name: 'as',
description: 'change the type of the value when used with index',
forceEnum: true,
enumProvider: commonEnumProviders.types,
isRequired: false,
defaultValue: 'string',
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
@@ -910,6 +923,7 @@ export function registerVariableCommands() {
helpString: `
<div>
Set a local variable value and pass it down the pipe. The <code>index</code> argument is optional.
To convert the value to a specific JSON type when using <code>index</code>, use the <code>as</code> argument.
</div>
<div>
<strong>Example:</strong>
@@ -917,6 +931,9 @@ export function registerVariableCommands() {
<li>
<pre><code class="language-stscript">/setvar key=color green</code></pre>
</li>
<li>
<pre><code class="language-stscript">/setvar key=ages index=John as=number 21</code></pre>
</li>
</ul>
</div>
`,
@@ -1015,6 +1032,14 @@ export function registerVariableCommands() {
new SlashCommandNamedArgument(
'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false,
),
SlashCommandNamedArgument.fromProps({
name: 'as',
description: 'change the type of the value when used with index',
forceEnum: true,
enumProvider: commonEnumProviders.types,
isRequired: false,
defaultValue: 'string',
}),
],
unnamedArgumentList: [
new SlashCommandArgument(
@@ -1024,6 +1049,7 @@ export function registerVariableCommands() {
helpString: `
<div>
Set a global variable value and pass it down the pipe. The <code>index</code> argument is optional.
To convert the value to a specific JSON type when using <code>index</code>, use the <code>as</code> argument.
</div>
<div>
<strong>Example:</strong>
@@ -1031,6 +1057,9 @@ export function registerVariableCommands() {
<li>
<pre><code class="language-stscript">/setglobalvar key=color green</code></pre>
</li>
<li>
<pre><code class="language-stscript">/setglobalvar key=ages index=John as=number 21</code></pre>
</li>
</ul>
</div>
`,
@@ -2030,6 +2059,14 @@ export function registerVariableCommands() {
false, // isRequired
false, // acceptsMultiple
),
SlashCommandNamedArgument.fromProps({
name: 'as',
description: 'change the type of the value when used with index',
forceEnum: true,
enumProvider: commonEnumProviders.types,
isRequired: false,
defaultValue: 'string',
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
@@ -2049,7 +2086,8 @@ export function registerVariableCommands() {
splitUnnamedArgumentCount: 1,
helpString: `
<div>
Get or set a variable.
Get or set a variable. Use <code>index</code> to access elements of a JSON-serialized list or dictionary.
To convert the value to a specific JSON type when using with <code>index</code>, use the <code>as</code> argument.
</div>
<div>
<strong>Examples:</strong>
@@ -2060,6 +2098,9 @@ export function registerVariableCommands() {
<li>
<pre><code class="language-stscript">/let x foo | /var key=x foo bar | /var x | /echo</code></pre>
</li>
<li>
<pre><code class="language-stscript">/let x {} | /var index=cool as=number x 1337 | /echo {{var::x}}</code></pre>
</li>
</ul>
</div>
`,

View File

@@ -1602,6 +1602,7 @@ body[data-stscript-style] .hljs.language-stscript {
.hljs-pipe {
color: var(--ac-style-color-punctuation);
}
.hljs-pipebreak {
color: var(--ac-style-color-type);
}
@@ -1695,6 +1696,7 @@ body[data-stscript-style] .hljs.language-stscript {
background-color: var(--ac-color-selectedBackground);
color: var(--ac-color-selectedText);
}
&.selected.not-selectable>* {
background-color: var(--ac-color-notSelectableBackground);
color: var(--ac-color-notSelectableText);
@@ -1776,11 +1778,13 @@ body[data-stscript-style] .hljs.language-stscript {
padding: 0.25em 0.25em 0.5em 0.25em;
border-bottom: 1px solid var(--ac-color-border);
> .head {
>.head {
display: flex;
gap: 0.5em;
}
> .head > .name, > .name {
>.head>.name,
>.name {
flex: 1 1 auto;
font-weight: bold;
color: var(--ac-color-text);
@@ -1790,20 +1794,25 @@ body[data-stscript-style] .hljs.language-stscript {
text-decoration: 1px dotted underline;
}
}
> .head > .source {
>.head>.source {
padding: 0 0.5em;
cursor: help;
display: flex;
align-items: center;
gap: 0.5em;
&.isThirdParty.isExtension {
color: #F89406;
}
&.isCore {
color: transparent;
&.isExtension {
color: #51A351;
}
&:after {
content: '';
order: -1;
@@ -2605,7 +2614,8 @@ select option:not(:checked) {
color: var(--golden) !important;
}
.world_set {
.world_set,
.toggleEnabled {
color: var(--active) !important;
}
@@ -5398,6 +5408,7 @@ body:not(.movingUI) .drawer-content.maximized {
.popup:has(.faPicker) {
/* Fix height for fa picker popup, otherwise search is making it resize weirdly */
height: 70%;
.popup-content {
display: flex;
flex-direction: column;
@@ -5407,7 +5418,8 @@ body:not(.movingUI) .drawer-content.maximized {
.faPicker-container {
display: flex;
flex-direction: column;
overflow: hidden;;
overflow: hidden;
;
}
.faQuery-container {
@@ -5417,22 +5429,23 @@ body:not(.movingUI) .drawer-content.maximized {
.faPicker {
flex: 1 1 auto;
overflow: auto;
gap: 1em;
gap: 1em;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(3.5em, 1fr));
.menu_button {
aspect-ratio: 1 / 1;
font-size: 2em;
height: 1lh;
line-height: 1.2;
padding: 0.25em;
width: unset;
box-sizing: content-box;
.menu_button {
aspect-ratio: 1 / 1;
font-size: 2em;
height: 1lh;
line-height: 1.2;
padding: 0.25em;
width: unset;
box-sizing: content-box;
&.hidden {
display: none;
}
}
}
}
.faPicker:not(:has(:not(.hidden)))::after {
@@ -5440,3 +5453,7 @@ body:not(.movingUI) .drawer-content.maximized {
color: var(--SmartThemeBodyColor);
opacity: 0.7;
}
#advanced-formatting-button div[contenteditable] {
overflow-wrap: anywhere;
}