Merge branch 'staging' into delete-newbie-mode

This commit is contained in:
Cohee 2024-09-01 23:39:21 +03:00
commit 4de3b10af2
14 changed files with 1785 additions and 1677 deletions

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -452,7 +452,9 @@ export const event_types = {
// TODO: Naming convention is inconsistent with other events // TODO: Naming convention is inconsistent with other events
CHARACTER_DELETED: 'characterDeleted', CHARACTER_DELETED: 'characterDeleted',
CHARACTER_DUPLICATED: 'character_duplicated', 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', FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',
WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate', WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',
OPEN_CHARACTER_LIBRARY: 'open_character_library', OPEN_CHARACTER_LIBRARY: 'open_character_library',
@ -3120,6 +3122,7 @@ class StreamingProcessor {
if (logprobs) { if (logprobs) {
this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [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)); await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));
} }
const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000; const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
@ -10136,7 +10139,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']; let text = chat[this_edit_mes_id]['mes'];
$(this).closest('.mes_block').find('.mes_text').empty(); $(this).closest('.mes_block').find('.mes_text').empty();
@ -10154,6 +10157,8 @@ jQuery(async function () {
)); ));
appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes')); appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes'));
addCopyToCodeBlocks($(this).closest('.mes')); addCopyToCodeBlocks($(this).closest('.mes'));
await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
this_edit_mes_id = undefined; this_edit_mes_id = undefined;
}); });

View File

@ -296,7 +296,7 @@ export async function favsToHotswap() {
//helpful instruction message if no characters are favorited //helpful instruction message if no characters are favorited
if (favs.length == 0) { 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; return;
} }

View File

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

View File

@ -7,7 +7,7 @@ import {
power_user, power_user,
context_presets, context_presets,
} from './power-user.js'; } from './power-user.js';
import { regexFromString, resetScrollHeight } from './utils.js'; import { regexFromString } from './utils.js';
/** /**
* @type {any[]} Instruct mode presets. * @type {any[]} Instruct mode presets.
@ -78,28 +78,29 @@ function migrateInstructModeSettings(settings) {
* Loads instruct mode settings from the given data object. * Loads instruct mode settings from the given data object.
* @param {object} data Settings data object. * @param {object} data Settings data object.
*/ */
export function loadInstructMode(data) { export async function loadInstructMode(data) {
if (data.instruct !== undefined) { if (data.instruct !== undefined) {
instruct_presets = data.instruct; instruct_presets = data.instruct;
} }
migrateInstructModeSettings(power_user.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 => { controls.forEach(control => {
const $element = $(`#${control.id}`); const $element = $(`#${control.id}`);
if (control.isCheckbox) { if (control.isCheckbox) {
$element.prop('checked', power_user.instruct[control.property]); $element.prop('checked', power_user.instruct[control.property]);
} else { } else {
$element.val(power_user.instruct[control.property]); $element[0].innerText = (power_user.instruct[control.property]);
} }
$element.on('input', async function () { $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(); saveSettingsDebounced();
if (!control.isCheckbox) {
await resetScrollHeight($element);
}
}); });
if (control.trigger) { 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) => { instruct_presets.forEach((preset) => {
const name = preset.name; const name = preset.name;
const option = document.createElement('option'); const option = document.createElement('option');
@ -604,11 +608,29 @@ jQuery(() => {
$('#instruct_system_same_as_user').on('input', function () { $('#instruct_system_same_as_user').on('input', function () {
const state = !!$(this).prop('checked'); const state = !!$(this).prop('checked');
$('#instruct_system_sequence').prop('disabled', state); if (state) {
$('#instruct_system_suffix').prop('disabled', 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 () { $('#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) { if (!power_user.instruct.bind_to_context) {
return; 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 () { $('#instruct_presets').on('change', function () {
const name = String($(this).find(':selected').val()); const name = String($(this).find(':selected').val());
const preset = instruct_presets.find(x => x.name === name); const preset = instruct_presets.find(x => x.name === name);
@ -641,7 +667,8 @@ jQuery(() => {
if (control.isCheckbox) { if (control.isCheckbox) {
$element.prop('checked', power_user.instruct[control.property]).trigger('input'); $element.prop('checked', power_user.instruct[control.property]).trigger('input');
} else { } else {
$element.val(power_user.instruct[control.property]).trigger('input'); $element[0].innerText = (power_user.instruct[control.property]);
$element.trigger('input');
} }
} }
}); });

View File

@ -1469,7 +1469,7 @@ async function loadPowerUserSettings(settings, data) {
$('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length); $('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length);
$('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(', ')); $('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(', '));
$('#auto_swipe_blacklist_threshold').val(power_user.auto_swipe_blacklist_threshold); $('#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); $('#custom_stopping_strings_macro').prop('checked', power_user.custom_stopping_strings_macro);
$('#fuzzy_search_checkbox').prop('checked', power_user.fuzzy_search); $('#fuzzy_search_checkbox').prop('checked', power_user.fuzzy_search);
$('#persona_show_notifications').prop('checked', power_user.persona_show_notifications); $('#persona_show_notifications').prop('checked', power_user.persona_show_notifications);
@ -1499,7 +1499,7 @@ async function loadPowerUserSettings(settings, data) {
$('#waifuMode').prop('checked', power_user.waifuMode); $('#waifuMode').prop('checked', power_user.waifuMode);
$('#movingUImode').prop('checked', power_user.movingUI); $('#movingUImode').prop('checked', power_user.movingUI);
$('#noShadowsmode').prop('checked', power_user.noShadows); $('#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); $('#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_enabled').prop('checked', power_user.auto_continue.enabled);
$('#auto_continue_allow_chat_completions').prop('checked', power_user.auto_continue.allow_chat_completions); $('#auto_continue_allow_chat_completions').prop('checked', power_user.auto_continue.allow_chat_completions);
@ -1600,7 +1600,7 @@ async function loadPowerUserSettings(settings, data) {
switchReducedMotion(); switchReducedMotion();
switchCompactInputArea(); switchCompactInputArea();
reloadMarkdownProcessor(power_user.render_formulas); reloadMarkdownProcessor(power_user.render_formulas);
loadInstructMode(data); await loadInstructMode(data);
await loadContextSettings(); await loadContextSettings();
loadMaxContextUnlocked(); loadMaxContextUnlocked();
switchWaifuMode(); switchWaifuMode();
@ -1747,13 +1747,14 @@ async function loadContextSettings() {
if (control.isCheckbox) { if (control.isCheckbox) {
$element.prop('checked', power_user.context[control.property]); $element.prop('checked', power_user.context[control.property]);
} else { } 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 // If the setting already exists, no need to duplicate it
// TODO: Maybe check the power_user object for the setting instead of a flag? // TODO: Maybe check the power_user object for the setting instead of a flag?
$element.on('input', async function () { $element.on('input keyup', async function () {
const value = control.isCheckbox ? !!$(this).prop('checked') : $(this).val(); const value = control.isCheckbox ? !!$(this).prop('checked') : $(this)[0].innerText;
if (control.isGlobalSetting) { if (control.isGlobalSetting) {
power_user[control.property] = value; power_user[control.property] = value;
} else { } else {
@ -1761,9 +1762,6 @@ async function loadContextSettings() {
} }
saveSettingsDebounced(); saveSettingsDebounced();
if (!control.isCheckbox) {
await resetScrollHeight($element);
}
}); });
}); });
@ -1777,7 +1775,7 @@ async function loadContextSettings() {
}); });
$('#context_presets').on('change', function () { $('#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); const preset = context_presets.find(x => x.name === name);
if (!preset) { if (!preset) {
@ -1802,9 +1800,8 @@ async function loadContextSettings() {
.prop('checked', control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property]) .prop('checked', control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property])
.trigger('input'); .trigger('input');
} else { } else {
$element $element[0].innerText = (control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property])
.val(control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property]) $element.trigger('input');
.trigger('input');
} }
} }
}); });
@ -3106,7 +3103,7 @@ $(document).ready(() => {
}); });
$('#start_reply_with').on('input', function () { $('#start_reply_with').on('input', function () {
power_user.user_prompt_bias = String($(this).val()); power_user.user_prompt_bias = String($(this)[0].innerText);
saveSettingsDebounced(); saveSettingsDebounced();
}); });
@ -3638,7 +3635,7 @@ $(document).ready(() => {
}); });
$('#custom_stopping_strings').on('input', function () { $('#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(); saveSettingsDebounced();
}); });

View File

@ -36,6 +36,8 @@ export const enumIcons = {
true: '✔️', true: '✔️',
false: '❌', false: '❌',
null: '🚫',
undefined: '❓',
// Value types // Value types
boolean: '🔲', boolean: '🔲',
@ -230,4 +232,19 @@ export const commonEnumProviders = {
enumTypes.enum, '💉'); 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 { SlashCommandClosure } from './SlashCommandClosure.js';
import { convertValueType } from '../utils.js';
export class SlashCommandScope { export class SlashCommandScope {
/**@type {string[]}*/ variableNames = []; /**@type {string[]}*/ variableNames = [];
@ -55,7 +56,7 @@ export class SlashCommandScope {
if (this.existsVariableInScope(key)) throw new SlashCommandScopeVariableExistsError(`Variable named "${key}" already exists.`); if (this.existsVariableInScope(key)) throw new SlashCommandScopeVariableExistsError(`Variable named "${key}" already exists.`);
this.variables[key] = value; this.variables[key] = value;
} }
setVariable(key, value, index = null) { setVariable(key, value, index = null, type = null) {
if (this.existsVariableInScope(key)) { if (this.existsVariableInScope(key)) {
if (index !== null && index !== undefined) { if (index !== null && index !== undefined) {
let v = this.variables[key]; let v = this.variables[key];
@ -63,13 +64,13 @@ export class SlashCommandScope {
v = JSON.parse(v); v = JSON.parse(v);
const numIndex = Number(index); const numIndex = Number(index);
if (Number.isNaN(numIndex)) { if (Number.isNaN(numIndex)) {
v[index] = value; v[index] = convertValueType(value, type);
} else { } else {
v[numIndex] = value; v[numIndex] = convertValueType(value, type);
} }
v = JSON.stringify(v); v = JSON.stringify(v);
} catch { } catch {
v[index] = value; v[index] = convertValueType(value, type);
} }
this.variables[key] = v; this.variables[key] = v;
} else { } else {
@ -78,7 +79,7 @@ export class SlashCommandScope {
return value; return value;
} }
if (this.parent) { 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}"`); 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 { power_user } from './power-user.js';
import { delay } from './utils.js'; import { delay } from './utils.js';
@ -268,7 +267,6 @@ export class SmoothEventSourceStream extends EventSourceStream {
hasFocus && await delay(getDelay(lastStr)); hasFocus && await delay(getDelay(lastStr));
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify(parsed.data) })); controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify(parsed.data) }));
lastStr = parsed.chunk; lastStr = parsed.chunk;
hasFocus && await eventSource.emit(event_types.SMOOTH_STREAM_TOKEN_RECEIVED, parsed.chunk);
} }
} catch (error) { } catch (error) {
console.debug('Smooth Streaming parsing error', 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 { collapseNewlines } from './power-user.js';
import { debounce_timeout } from './constants.js'; import { debounce_timeout } from './constants.js';
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
/** /**
* Pagination status string template. * 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. * Parses ranges like 10-20 or 10.
* Range is inclusive. Start must be less than end. * 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 { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommandScope } from './slash-commands/SlashCommandScope.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/SlashCommandParser.js').NamedArguments} NamedArguments */
/** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */ /** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */
@ -57,12 +57,12 @@ function setLocalVariable(name, value, args = {}) {
if (localVariable === null) { if (localVariable === null) {
localVariable = {}; localVariable = {};
} }
localVariable[args.index] = value; localVariable[args.index] = convertValueType(value, args.as);
} else { } else {
if (localVariable === null) { if (localVariable === null) {
localVariable = []; localVariable = [];
} }
localVariable[numIndex] = value; localVariable[numIndex] = convertValueType(value, args.as);
} }
chat_metadata.variables[name] = JSON.stringify(localVariable); chat_metadata.variables[name] = JSON.stringify(localVariable);
} catch { } catch {
@ -106,12 +106,12 @@ function setGlobalVariable(name, value, args = {}) {
if (globalVariable === null) { if (globalVariable === null) {
globalVariable = {}; globalVariable = {};
} }
globalVariable[args.index] = value; globalVariable[args.index] = convertValueType(value, args.as);
} else { } else {
if (globalVariable === null) { if (globalVariable === null) {
globalVariable = []; globalVariable = [];
} }
globalVariable[numIndex] = value; globalVariable[numIndex] = convertValueType(value, args.as);
} }
extension_settings.variables.global[name] = JSON.stringify(globalVariable); extension_settings.variables.global[name] = JSON.stringify(globalVariable);
} catch { } catch {
@ -667,23 +667,28 @@ function parseNumericSeries(value, scope = null) {
} }
function performOperation(value, operation, singleOperand = false, scope = null) { function performOperation(value, operation, singleOperand = false, scope = null) {
if (!value) { function getResult() {
return 0; 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); const result = getResult();
return String(result);
if (array.length === 0) {
return 0;
}
const result = singleOperand ? operation(array[0]) : operation(array);
if (isNaN(result) || !isFinite(result)) {
return 0;
}
return result;
} }
function addValuesCallback(args, value) { 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 (typeof key != 'string') throw new Error('Key must be a string');
if (args._hasUnnamedArgument) { if (args._hasUnnamedArgument) {
const val = typeof value[0] == 'string' ? value.join(' ') : value[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; return val;
} else { } else {
return args._scope.getVariable(key, args.index); 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 (typeof key != 'string') throw new Error('Key must be a string');
if (value.length > 0) { if (value.length > 0) {
const val = typeof value[0] == 'string' ? value.join(' ') : value[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; return val;
} else { } else {
return args._scope.getVariable(key, args.index); return args._scope.getVariable(key, args.index);
@ -901,6 +906,14 @@ export function registerVariableCommands() {
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, '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: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -910,6 +923,7 @@ export function registerVariableCommands() {
helpString: ` helpString: `
<div> <div>
Set a local variable value and pass it down the pipe. The <code>index</code> argument is optional. 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>
<div> <div>
<strong>Example:</strong> <strong>Example:</strong>
@ -917,6 +931,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/setvar key=color green</code></pre> <pre><code class="language-stscript">/setvar key=color green</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/setvar key=ages index=John as=number 21</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,
@ -1015,6 +1032,14 @@ export function registerVariableCommands() {
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, '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: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -1024,6 +1049,7 @@ export function registerVariableCommands() {
helpString: ` helpString: `
<div> <div>
Set a global variable value and pass it down the pipe. The <code>index</code> argument is optional. 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>
<div> <div>
<strong>Example:</strong> <strong>Example:</strong>
@ -1031,6 +1057,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/setglobalvar key=color green</code></pre> <pre><code class="language-stscript">/setglobalvar key=color green</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/setglobalvar key=ages index=John as=number 21</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,
@ -2030,6 +2059,14 @@ export function registerVariableCommands() {
false, // isRequired false, // isRequired
false, // acceptsMultiple 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: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
@ -2049,7 +2086,8 @@ export function registerVariableCommands() {
splitUnnamedArgumentCount: 1, splitUnnamedArgumentCount: 1,
helpString: ` helpString: `
<div> <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>
<div> <div>
<strong>Examples:</strong> <strong>Examples:</strong>
@ -2060,6 +2098,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/let x foo | /var key=x foo bar | /var x | /echo</code></pre> <pre><code class="language-stscript">/let x foo | /var key=x foo bar | /var x | /echo</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/let x {} | /var index=cool as=number x 1337 | /echo {{var::x}}</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,

View File

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