SillyTavern/public/scripts/authors-note.js
Len 1d75b98393
STscript Parser Rewrite (#1965)
* set isForced to true on input

* make floating auto-complete follow horizontal scrolling

* add callable closure vars

* changes to /let and /var for callable closures

* fix error message

* fix scope for closure arguments

* if should return the pipe result from closures

* use /run to call closures and no arguments on immediate closures

* throw exception from QRs window-function if no match

* when to show autocomplete vs info only

* autocomplete positioning

* autocomplete styling

* add theming to autocomplete (theme, dark, light)

* improve autocomplete show/hide logic and editor selection

* use blur tint color instead of chat tint color and use blur setting

* cleanup and docs

* use scope macros for QR args

* add enter to select autocomplete

* fix no executor found

* cleanup and comment

* fix alias list in help string

* fallback to empty string piped value if null or undefined

* fix typo

* blur textarea on ctrl+enter execute (and refocus after)

* stop executeSlashCommand if parser throws

* move /let and /var callbacks into functions

* switch textarea to monospace when value starts with slash

* add double pipe a pipe breaker

* fix /? slash

* remove some logging

* add "/:name" as shorthand for "/run name" after all

* move shit around

* fix error message

* use testRunShorthandEnd

* use parseQuotedValue and parseValue to determine name for "/:"

QR labels and set names can include spaces

* add some adjustments to make autocomplete work properly

some hint in there about "/:" would still be nice

* add autocomplete style  selector

* only strip quotes from subcommand if they are at both ends

* fix JSDoc

* escaping

* allow open quotes on dry run

* throwing shit at the wall for /: autocomplete

* escapes only for symbols

* clean up autocomplete

* improve performance

* fix scope macros

* remove unescaping of pipes

* fix macros in scope copy

* fix "/? slash"

* don't run parser for getNameAt if text has not changed

* fix options filter

* re-enable blur listener

* restore selection on non-replace select

* fix for escaping first character of value

* add support for {{pipe}} and {{var::}} closures

* add index support to var macro

* add scoped var macro to macro help

* more escape fixes

* reduce autocomplete render debounce

* cleanup

* restore old escape handling and parser flag for strict escaping

* fix "no match" autocomplete message

* add dummy commands for comments and parser flag

* fix type annotations

* somewhat safer macro replacements

* fix autocomplete select on blank / "no match"

* fix cutting off handled part in substitution

* add parser flag REPLACE_GETVAR

Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}.
Inserts a series of command executors before the command with the macros that:
- save {{pipe}} to a var
- call /getvar or /getglobalvar to get the variable used in the macro
- call /let to save the retrieved variable
- return the saved {{pipe}} value

This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros.

* remove old parser

* fix send on enter when no match

* deal with pipes in quoted values (loose escaping)

* add default parser flags to user settings

* allow quoted values in unnamed argument

* set parser flag without explicit state to "on"

* add click hint on parser error toast

* dirty more detailed cmd defs

* remove name from unnamed arg

* move autocomplete into class and floating with details

* replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event

* fix ctrl+space

* fix arrow navigation

* add comments

* fix pointer block

* add static fromProps

* fix up dummy commands

* migrate all commands to addCommandObject

* remove commented comment command

* fix alias in details

* add range as argument type

* switch to addCommandObject

* switch to addCommandObject

* fix height

* fix floating details position on left

* re-enable blur event

* use auto width for full details on floating autocomplete

* auto-size floating full details

* fix typo

* re-enable blur listener

* don't prevent enter when selected item is fully typed out

* add autocomplete details tooltips

* add language to slash command examples

* move makeItem into option and command and fix click select

* use autocomplete parts in /? slash

* fix alias formatting

* add language to slash command examples

* fix details position on initial input history

* small screen styles

* replace registerSlashCommand with detailed declarations

* put name on first line

* add missing returns

* fix missing comma

* fix alias display in autocomplete list

* remove args from help string

* move parser settings to its own section

* jsdoc

* hljs stscript lang

* add hljs to autocomplete help examples

* add missing import

* apply autocomplete colors to stscript codeblocks (hljs)

* add fromProps

* cache autocomplete elements

* towards generic autocomplete

* remove unused imports

* fix blanks

* add return types

* re-enable blur

* fix blank check

* Caption messages by id

* add aborting command execution

* fix return type

* fix chat input font reset

* add slash command progress indicator

* add missing return

* mark registerSlashCommand deprecated

* why??

* separate abort logic for commands

* remove parsing of quoted values from unnamed arg

* add adjustable autocomplete width

* revert stop button pulse

* add progress and pause/abort to QR editor

* add resize event on autocomplete width change

* add key= argument to all get vars

* refactoring

* introduce NamedArgumentAsignment

* add TODOs

* refactoring

* record start and end of named arg assignment

* refactoring

* prevent duplicate calls to show

* refactoring

* remove macro ac

* add secondary autocomplete and enum descriptions

* add syntax highlighting to QR editor

* add enum descriptions to /while

* add /let key=... to scope variable names

* add unnamed argument assignment class and unnamed argument splitting

* fix QR editor style

* remove dash before autocomplete help text

* add autocomplete for unnamed enums

* fix remaining dom after holding backslash

* fix for unnamed enums

* fix autocomplete for /parser-flag

* add parser-flag enum help

* fix type annotations

* fix autocomplete result for /:

* add colored autocomplete type icons

* collapse second line autocomplete help if empty

* mark optional named args in autocomplete

* fix when what

* remove duplicate debug buttons

* dispatch input on autocomplete select

* prevent grow from editor syntax layer

* add auto-adjust qr editor caret color

* remove text-shadow from autocomplete

* join value strings in /let and /var

* add /abort syntax highlight

* fix attempting secondary result when there is none

* rename settings headers and split autocomplete / stscript

* add parser flag tooltips

* add tooltips to chat width stops

* fix typo

* return clone of help item

* fix enum string

* don't make optional notice for autocomplete arguments smaller

* avoid scrollbar in chat input

* add rudimentary macro autocomplete

* strip macro from helptext

* finally remove closure delimiters around root

* cleanup

* fix index stuff for removed closure delimiters

* fix type hint

* add child commands to progress indicator

* include sub-separator in macro autocomplete

* remove all mentions of interruptsGeneration and purge

* remove unused imports

* fix syntax highlight with newline at end of input

* cleanup select pointer events

* coalesce onProgress call

* add regex to STscript syntax highlighting

* fix closure end

* fix autocomplete type icon alignment

* adjustments for small screens

* fix removing wrong element

* add missing "at=" arg to /sys, /comment, /sendas

* add font scale setting for autocomplete

* add target=_blank for parser flag links

* fix for searching enums

* remove REGEXP_MODE from hljs
just causes trouble

* fix autocomplete in closures

* fix typo

* fix type hint

* Get rid of scroll bar on load

* Add type hint for /send name argument. Fix 'at' types

* Add 'negative' arg hint to /sd command

* reenable blur event

* Allow /summarize to process any text

* Compact layout of script toggles

* Expand CSS by default

* fix double ranger indicator and adjust to narrow container

* make custom css input fill available vertical space

* reduce scroll lag

* use default cursor on scrollbar

* Clean-up module loading in index.html

* fix tab indent with hljs

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 22:15:05 +03:00

516 lines
19 KiB
JavaScript

import {
animation_duration,
chat_metadata,
eventSource,
event_types,
extension_prompt_roles,
saveSettingsDebounced,
this_chid,
} from '../script.js';
import { selected_group } from './group-chats.js';
import { extension_settings, getContext, saveMetadataDebounced } from './extensions.js';
import { getCharaFilename, debounce, delay } from './utils.js';
import { getTokenCountAsync } from './tokenizers.js';
import { debounce_timeout } from './constants.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
export { MODULE_NAME as NOTE_MODULE_NAME };
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
export var shouldWIAddPrompt = false;
export const metadata_keys = {
prompt: 'note_prompt',
interval: 'note_interval',
depth: 'note_depth',
position: 'note_position',
role: 'note_role',
};
const chara_note_position = {
replace: 0,
before: 1,
after: 2,
};
function setNoteTextCommand(_, text) {
$('#extension_floating_prompt').val(text).trigger('input');
toastr.success('Author\'s Note text updated');
}
function setNoteDepthCommand(_, text) {
const value = Number(text);
if (Number.isNaN(value)) {
toastr.error('Not a valid number');
return;
}
$('#extension_floating_depth').val(Math.abs(value)).trigger('input');
toastr.success('Author\'s Note depth updated');
}
function setNoteIntervalCommand(_, text) {
const value = Number(text);
if (Number.isNaN(value)) {
toastr.error('Not a valid number');
return;
}
$('#extension_floating_interval').val(Math.abs(value)).trigger('input');
toastr.success('Author\'s Note frequency updated');
}
function setNotePositionCommand(_, text) {
const validPositions = {
'scenario': 0,
'chat': 1,
};
const position = validPositions[text?.trim()];
if (Number.isNaN(position)) {
toastr.error('Not a valid position');
return;
}
$(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input');
toastr.info('Author\'s Note position updated');
}
function updateSettings() {
saveSettingsDebounced();
loadSettings();
setFloatingPrompt();
}
const setMainPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_prompt_token_counter').text(await getTokenCountAsync(value)), debounce_timeout.relaxed);
const setCharaPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_chara_token_counter').text(await getTokenCountAsync(value)), debounce_timeout.relaxed);
const setDefaultPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_default_token_counter').text(await getTokenCountAsync(value)), debounce_timeout.relaxed);
async function onExtensionFloatingPromptInput() {
chat_metadata[metadata_keys.prompt] = $(this).val();
setMainPromptTokenCounterDebounced(chat_metadata[metadata_keys.prompt]);
updateSettings();
saveMetadataDebounced();
}
async function onExtensionFloatingIntervalInput() {
chat_metadata[metadata_keys.interval] = Number($(this).val());
updateSettings();
saveMetadataDebounced();
}
async function onExtensionFloatingDepthInput() {
let value = Number($(this).val());
if (value < 0) {
value = Math.abs(value);
$(this).val(value);
}
chat_metadata[metadata_keys.depth] = value;
updateSettings();
saveMetadataDebounced();
}
async function onExtensionFloatingPositionInput(e) {
chat_metadata[metadata_keys.position] = Number(e.target.value);
updateSettings();
saveMetadataDebounced();
}
async function onDefaultPositionInput(e) {
extension_settings.note.defaultPosition = Number(e.target.value);
saveSettingsDebounced();
}
async function onDefaultDepthInput() {
let value = Number($(this).val());
if (value < 0) {
value = Math.abs(value);
$(this).val(value);
}
extension_settings.note.defaultDepth = value;
saveSettingsDebounced();
}
async function onDefaultIntervalInput() {
extension_settings.note.defaultInterval = Number($(this).val());
saveSettingsDebounced();
}
function onExtensionFloatingRoleInput(e) {
chat_metadata[metadata_keys.role] = Number(e.target.value);
updateSettings();
}
function onExtensionDefaultRoleInput(e) {
extension_settings.note.defaultRole = Number(e.target.value);
saveSettingsDebounced();
}
async function onExtensionFloatingCharPositionInput(e) {
const value = e.target.value;
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
if (charaNote) {
charaNote.position = Number(value);
updateSettings();
}
}
function onExtensionFloatingCharaPromptInput() {
const tempPrompt = $(this).val();
const avatarName = getCharaFilename();
let tempCharaNote = {
name: avatarName,
prompt: tempPrompt,
};
setCharaPromptTokenCounterDebounced(tempPrompt);
let existingCharaNoteIndex;
let existingCharaNote;
if (extension_settings.note.chara) {
existingCharaNoteIndex = extension_settings.note.chara.findIndex((e) => e.name === avatarName);
existingCharaNote = extension_settings.note.chara[existingCharaNoteIndex];
}
if (tempPrompt.length === 0 &&
extension_settings.note.chara &&
existingCharaNote &&
!existingCharaNote.useChara
) {
extension_settings.note.chara.splice(existingCharaNoteIndex, 1);
}
else if (extension_settings.note.chara && existingCharaNote) {
Object.assign(existingCharaNote, tempCharaNote);
}
else if (avatarName && tempPrompt.length > 0) {
if (!extension_settings.note.chara) {
extension_settings.note.chara = [];
}
Object.assign(tempCharaNote, { useChara: false, position: chara_note_position.replace });
extension_settings.note.chara.push(tempCharaNote);
} else {
console.log('Character author\'s note error: No avatar name key could be found.');
toastr.error('Something went wrong. Could not save character\'s author\'s note.');
// Don't save settings if something went wrong
return;
}
updateSettings();
}
function onExtensionFloatingCharaCheckboxChanged() {
const value = !!$(this).prop('checked');
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
if (charaNote) {
charaNote.useChara = value;
updateSettings();
}
}
function onExtensionFloatingDefaultInput() {
extension_settings.note.default = $(this).val();
setDefaultPromptTokenCounterDebounced(extension_settings.note.default);
updateSettings();
}
function loadSettings() {
const DEFAULT_DEPTH = 4;
const DEFAULT_POSITION = 1;
const DEFAULT_INTERVAL = 1;
const DEFAULT_ROLE = extension_prompt_roles.SYSTEM;
if (extension_settings.note.defaultPosition === undefined) {
extension_settings.note.defaultPosition = DEFAULT_POSITION;
}
if (extension_settings.note.defaultDepth === undefined) {
extension_settings.note.defaultDepth = DEFAULT_DEPTH;
}
if (extension_settings.note.defaultInterval === undefined) {
extension_settings.note.defaultInterval = DEFAULT_INTERVAL;
}
if (extension_settings.note.defaultRole === undefined) {
extension_settings.note.defaultRole = DEFAULT_ROLE;
}
chat_metadata[metadata_keys.prompt] = chat_metadata[metadata_keys.prompt] ?? extension_settings.note.default ?? '';
chat_metadata[metadata_keys.interval] = chat_metadata[metadata_keys.interval] ?? extension_settings.note.defaultInterval ?? DEFAULT_INTERVAL;
chat_metadata[metadata_keys.position] = chat_metadata[metadata_keys.position] ?? extension_settings.note.defaultPosition ?? DEFAULT_POSITION;
chat_metadata[metadata_keys.depth] = chat_metadata[metadata_keys.depth] ?? extension_settings.note.defaultDepth ?? DEFAULT_DEPTH;
chat_metadata[metadata_keys.role] = chat_metadata[metadata_keys.role] ?? extension_settings.note.defaultRole ?? DEFAULT_ROLE;
$('#extension_floating_prompt').val(chat_metadata[metadata_keys.prompt]);
$('#extension_floating_interval').val(chat_metadata[metadata_keys.interval]);
$('#extension_floating_allow_wi_scan').prop('checked', extension_settings.note.allowWIScan ?? false);
$('#extension_floating_depth').val(chat_metadata[metadata_keys.depth]);
$('#extension_floating_role').val(chat_metadata[metadata_keys.role]);
$(`input[name="extension_floating_position"][value="${chat_metadata[metadata_keys.position]}"]`).prop('checked', true);
if (extension_settings.note.chara && getContext().characterId) {
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
$('#extension_floating_chara').val(charaNote ? charaNote.prompt : '');
$('#extension_use_floating_chara').prop('checked', charaNote ? charaNote.useChara : false);
$(`input[name="extension_floating_char_position"][value="${charaNote?.position ?? chara_note_position.replace}"]`).prop('checked', true);
} else {
$('#extension_floating_chara').val('');
$('#extension_use_floating_chara').prop('checked', false);
$(`input[name="extension_floating_char_position"][value="${chara_note_position.replace}"]`).prop('checked', true);
}
$('#extension_floating_default').val(extension_settings.note.default);
$('#extension_default_depth').val(extension_settings.note.defaultDepth);
$('#extension_default_interval').val(extension_settings.note.defaultInterval);
$('#extension_default_role').val(extension_settings.note.defaultRole);
$(`input[name="extension_default_position"][value="${extension_settings.note.defaultPosition}"]`).prop('checked', true);
}
export function setFloatingPrompt() {
const context = getContext();
if (!context.groupId && context.characterId === undefined) {
console.debug('setFloatingPrompt: Not in a chat. Skipping.');
shouldWIAddPrompt = false;
return;
}
// take the count of messages
let lastMessageNumber = Array.isArray(context.chat) && context.chat.length ? context.chat.filter(m => m.is_user).length : 0;
console.debug(`
setFloatingPrompt entered
------
lastMessageNumber = ${lastMessageNumber}
metadata_keys.interval = ${chat_metadata[metadata_keys.interval]}
metadata_keys.position = ${chat_metadata[metadata_keys.position]}
metadata_keys.depth = ${chat_metadata[metadata_keys.depth]}
metadata_keys.role = ${chat_metadata[metadata_keys.role]}
------
`);
// interval 1 should be inserted no matter what
if (chat_metadata[metadata_keys.interval] === 1) {
lastMessageNumber = 1;
}
if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
context.setExtensionPrompt(MODULE_NAME, '');
$('#extension_floating_counter').text('(disabled)');
shouldWIAddPrompt = false;
return;
}
const messagesTillInsertion = lastMessageNumber >= chat_metadata[metadata_keys.interval]
? (lastMessageNumber % chat_metadata[metadata_keys.interval])
: (chat_metadata[metadata_keys.interval] - lastMessageNumber);
const shouldAddPrompt = messagesTillInsertion == 0;
shouldWIAddPrompt = shouldAddPrompt;
let prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : '';
if (shouldAddPrompt && extension_settings.note.chara && getContext().characterId) {
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
// Only replace with the chara note if the user checked the box
if (charaNote && charaNote.useChara) {
switch (charaNote.position) {
case chara_note_position.before:
prompt = charaNote.prompt + '\n' + prompt;
break;
case chara_note_position.after:
prompt = prompt + '\n' + charaNote.prompt;
break;
default:
prompt = charaNote.prompt;
break;
}
}
}
context.setExtensionPrompt(
MODULE_NAME,
prompt,
chat_metadata[metadata_keys.position],
chat_metadata[metadata_keys.depth],
extension_settings.note.allowWIScan,
chat_metadata[metadata_keys.role],
);
$('#extension_floating_counter').text(shouldAddPrompt ? '0' : messagesTillInsertion);
}
function onANMenuItemClick() {
if (selected_group || this_chid) {
//show AN if it's hidden
if ($('#floatingPrompt').css('display') !== 'flex') {
$('#floatingPrompt').addClass('resizing');
$('#floatingPrompt').css('display', 'flex');
$('#floatingPrompt').css('opacity', 0.0);
$('#floatingPrompt').transition({
opacity: 1.0,
duration: animation_duration,
}, async function () {
await delay(50);
$('#floatingPrompt').removeClass('resizing');
});
//auto-open the main AN inline drawer
if ($('#ANBlockToggle')
.siblings('.inline-drawer-content')
.css('display') !== 'block') {
$('#floatingPrompt').addClass('resizing');
$('#ANBlockToggle').click();
}
} else {
//hide AN if it's already displayed
$('#floatingPrompt').addClass('resizing');
$('#floatingPrompt').transition({
opacity: 0.0,
duration: animation_duration,
},
async function () {
await delay(50);
$('#floatingPrompt').removeClass('resizing');
});
setTimeout(function () {
$('#floatingPrompt').hide();
}, animation_duration);
}
//duplicate options menu close handler from script.js
//because this listener takes priority
$('#options').stop().fadeOut(animation_duration);
} else {
toastr.warning('Select a character before trying to use Author\'s Note', '', { timeOut: 2000 });
}
}
async function onChatChanged() {
loadSettings();
setFloatingPrompt();
const context = getContext();
// Disable the chara note if in a group
$('#extension_floating_chara').prop('disabled', context.groupId ? true : false);
const tokenCounter1 = chat_metadata[metadata_keys.prompt] ? await getTokenCountAsync(chat_metadata[metadata_keys.prompt]) : 0;
$('#extension_floating_prompt_token_counter').text(tokenCounter1);
let tokenCounter2;
if (extension_settings.note.chara && context.characterId) {
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
if (charaNote) {
tokenCounter2 = await getTokenCountAsync(charaNote.prompt);
}
}
$('#extension_floating_chara_token_counter').text(tokenCounter2 || 0);
const tokenCounter3 = extension_settings.note.default ? await getTokenCountAsync(extension_settings.note.default) : 0;
$('#extension_floating_default_token_counter').text(tokenCounter3);
}
function onAllowWIScanCheckboxChanged() {
extension_settings.note.allowWIScan = !!$(this).prop('checked');
updateSettings();
}
/**
* Inject author's note options and setup event listeners.
*/
// Inserts the extension first since it's statically imported
export function initAuthorsNote() {
$('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput);
$('#extension_floating_interval').on('input', onExtensionFloatingIntervalInput);
$('#extension_floating_depth').on('input', onExtensionFloatingDepthInput);
$('#extension_floating_chara').on('input', onExtensionFloatingCharaPromptInput);
$('#extension_use_floating_chara').on('input', onExtensionFloatingCharaCheckboxChanged);
$('#extension_floating_default').on('input', onExtensionFloatingDefaultInput);
$('#extension_default_depth').on('input', onDefaultDepthInput);
$('#extension_default_interval').on('input', onDefaultIntervalInput);
$('#extension_floating_allow_wi_scan').on('input', onAllowWIScanCheckboxChanged);
$('#extension_floating_role').on('input', onExtensionFloatingRoleInput);
$('#extension_default_role').on('input', onExtensionDefaultRoleInput);
$('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput);
$('input[name="extension_default_position"]').on('change', onDefaultPositionInput);
$('input[name="extension_floating_char_position"]').on('change', onExtensionFloatingCharPositionInput);
$('#ANClose').on('click', function () {
$('#floatingPrompt').transition({
opacity: 0,
duration: animation_duration,
easing: 'ease-in-out',
});
setTimeout(function () { $('#floatingPrompt').hide(); }, animation_duration);
});
$('#option_toggle_AN').on('click', onANMenuItemClick);
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'note',
callback: setNoteTextCommand,
unnamedArgumentList: [
new SlashCommandArgument(
'text', [ARGUMENT_TYPE.STRING], true,
),
],
helpString: `
<div>
Sets an author's note for the currently selected chat.
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'depth',
callback: setNoteDepthCommand,
unnamedArgumentList: [
new SlashCommandArgument(
'number', [ARGUMENT_TYPE.NUMBER], true,
),
],
helpString: `
<div>
Sets an author's note depth for in-chat positioning.
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'freq',
callback: setNoteIntervalCommand,
namedArgumentList: [],
unnamedArgumentList: [
new SlashCommandArgument(
'number', [ARGUMENT_TYPE.NUMBER], true,
),
],
helpString: `
<div>
Sets an author's note insertion frequency.
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'pos',
callback: setNotePositionCommand,
namedArgumentList: [],
unnamedArgumentList: [
new SlashCommandArgument(
'position', [ARGUMENT_TYPE.STRING], true, false, null, ['chat', 'scenario'],
),
],
helpString: `
<div>
Sets an author's note position.
</div>
`,
}));
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
}