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) {
#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

@ -452,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',
@ -3120,6 +3122,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;
@ -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'];
$(this).closest('.mes_block').find('.mes_text').empty();
@ -10154,6 +10157,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;
});

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;
}

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

@ -1469,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);
@ -1499,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);
@ -1600,7 +1600,7 @@ async function loadPowerUserSettings(settings, data) {
switchReducedMotion();
switchCompactInputArea();
reloadMarkdownProcessor(power_user.render_formulas);
loadInstructMode(data);
await loadInstructMode(data);
await loadContextSettings();
loadMaxContextUnlocked();
switchWaifuMode();
@ -1747,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 {
@ -1761,9 +1762,6 @@ async function loadContextSettings() {
}
saveSettingsDebounced();
if (!control.isCheckbox) {
await resetScrollHeight($element);
}
});
});
@ -1777,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) {
@ -1802,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');
}
}
});
@ -3106,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();
});
@ -3638,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();
});

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;
}