Skip to content

UI & Popups

APIs for showing dialogs, blocking the UI during long operations, rendering plugin templates, and formatting message content.

Popups

ts
new Popup(content: string | HTMLElement | JQuery, type: POPUP_TYPE, inputValue?: string, options?: PopupOptions): Popup

The full-control popup primitive. Use this when you need a result, custom buttons, or input controls.

PopupOptions fieldDescription
okButton / cancelButtonCustom button text. Pass false to hide
rowsInput rows (INPUT type)
placeholderInput placeholder
tooltipTooltip on the popup body
wide / wider / largeSize presets
transparentTransparent background
defaultResultDefault POPUP_RESULT when escape-closed
customButtonsArray of { text, tooltip?, result?, classes?, icon?, action?, appendAtEnd? }
customInputsArray of { id, label, tooltip?, defaultState?, type?, rows?, min?, max?, step?, disabled? }
allowEscapeCloseAllow Esc to dismiss
onOpen(popup) / onClosing(popup) / onClose(popup)Lifecycle hooks
cropAspect / cropImageCROP type configuration
MethodReturnsNotes
await popup.show()Promise<string | number | boolean | null>Append + display. Resolves with the result value
await popup.complete(result)Promise<void>Programmatic close. Returns undefined if onClosing cancelled
popup.completeAffirmative()Shortcut for OK
popup.completeNegative()Shortcut for Cancel
popup.completeCancelled()Shortcut for Esc

popup.value holds the input string for INPUT type; popup.cropData holds the data URL for CROP type.

ts
context.POPUP_TYPE: {
    TEXT: 1,        // content + buttons
    CONFIRM: 2,     // Yes / No focus
    INPUT: 3,       // text input, returns value
    DISPLAY: 4,     // content only, X-close
    CROP: 5,        // image crop, returns data URL
}
ts
context.POPUP_RESULT: {
    AFFIRMATIVE: 1,
    NEGATIVE: 0,
    CANCELLED: null,
    CUSTOM1: 1001,
    // ...
    CUSTOM9: 1009,
}

Custom buttons that omit a result field receive auto-assigned CUSTOM1CUSTOM9 values.

callGenericPopup

ts
callGenericPopup(
    content: string | HTMLElement | JQuery,
    type: POPUP_TYPE,
    inputValue?: string,
    popupOptions?: PopupOptions,
): Promise<POPUP_RESULT | string | boolean | null>

Function-style shortcut equivalent to new Popup(...).show(). Use this when you don't need to hold a reference to the popup instance.

js
const ctx = Luker.getContext();

// Confirm
const result = await ctx.callGenericPopup(
    'Delete this conversation?',
    ctx.POPUP_TYPE.CONFIRM,
);
if (result === ctx.POPUP_RESULT.AFFIRMATIVE) {
    // ...
}

// Input
const userInput = await ctx.callGenericPopup(
    'Enter your name:',
    ctx.POPUP_TYPE.INPUT,
    'Anonymous',
    { rows: 1 },
);

// Display only
await ctx.callGenericPopup(
    '<h3>Done</h3><p>Plugin initialized.</p>',
    ctx.POPUP_TYPE.DISPLAY,
);

callPopup (deprecated)

ts
callPopup(text: string, type: string, ...): Promise<any>

Legacy popup helper using string-keyed types ('text', 'confirm', 'input', etc.). Migrate to callGenericPopup with the numeric POPUP_TYPE values.

Loader

loader.show / loader.hide

ts
loader.show(options?: ActionLoaderOptions): ActionLoaderHandle
loader.hide(handle?: ActionLoaderHandle): Promise<void>

Recommended API for blocking the UI during a long operation. loader.show() returns a handle; pass it back to loader.hide(handle) to dismiss specifically that loader. loader.hide() with no argument hides all loaders.

ActionLoaderOptions fieldDefaultDescription
blockingtrueWhether the overlay blocks input
toastMode'stoppable''none' / 'static' / 'stoppable'
slugnullOptional ID for handle lookup
message'Generating...'Text shown in the overlay
title''Optional title above the message
stopTooltip'Stop'Tooltip on the stop button
overlayContentnullCustom DOM content
onStopnullCalled when user clicks Stop
onHidenullCalled when the loader is hidden
js
const ctx = Luker.getContext();

const handle = ctx.loader.show({
    message: 'Importing...',
    toastMode: 'stoppable',
    onStop: () => abortController.abort(),
});

try {
    await doImport();
} finally {
    await ctx.loader.hide(handle);
}

loader namespace utilities

MethodDescription
loader.active()All currently-active handles
loader.get(id)Lookup a handle by id
loader.isBlocking()Whether any blocking loader is active
loader.ToastModeEnum of toast modes
loader.HandleThe ActionLoaderHandle class
loader.createOverlay()Build a default overlay element

showLoader / hideLoader (deprecated)

Older entry points. Migrate to loader.show / loader.hide. The legacy showLoader() is now a thin wrapper that delegates to the modern API.

Templates

renderExtensionTemplateAsync

ts
renderExtensionTemplateAsync(
    extensionName: string,
    templateId: string,
    templateData?: object,
    sanitize?: boolean,
    localize?: boolean,
): Promise<string>

Loads an HTML template from scripts/extensions/${extensionName}/${templateId}.html and returns the rendered HTML. Sanitization (DOMPurify) and localization (i18n auto-translation) are applied.

For a third-party extension at scripts/extensions/third-party/MyExt/dialog.html:

js
const ctx = Luker.getContext();
const html = await ctx.renderExtensionTemplateAsync('third-party/MyExt', 'dialog', {
    title: 'Settings',
    items: ['a', 'b', 'c'],
});
const popup = new ctx.Popup(html, ctx.POPUP_TYPE.DISPLAY);
await popup.show();

renderExtensionTemplate (deprecated)

Synchronous variant. Migrate to the async version — the underlying loader is async and the sync version blocks the event loop.

Message Formatting

messageFormatting

ts
messageFormatting(
    mes: string,
    ch_name: string,
    isSystem: boolean,
    isUser: boolean,
    messageId: number,
    sanitizerOverrides?: object,
    isReasoning?: boolean,
): string

Returns the rendered HTML for a message, applying:

  • Markdown rendering
  • Custom CSS class injection
  • Code syntax highlighting
  • Macro substitution
  • Regex pipeline (with placement AI_OUTPUT / USER_INPUT based on flags)

Use this when rendering message-like content in plugin UI (e.g., a preview popup) so it matches the styling of the chat.

js
const ctx = Luker.getContext();
const html = ctx.messageFormatting(
    rawText,
    'Preview',
    /* isSystem */ false,
    /* isUser */ false,
    /* messageId */ -1,
);

Utility Wrappers

ModuleWorkerWrapper

ts
new ModuleWorkerWrapper(updateFn: () => Promise<void>): { update(): Promise<void> }

Mutex wrapper for periodic worker functions — prevents overlapping ticks when the previous run hasn't finished. Typical pattern:

js
const ctx = Luker.getContext();

const worker = new ctx.ModuleWorkerWrapper(async () => {
    await doExpensiveTick();
});

// Trigger periodically; concurrent calls serialize
setInterval(() => worker.update(), 5000);

Toasts

Toast notifications use the global toastr library (see toastr.js docs). Available everywhere — not exposed through getContext() because it's already a global:

js
toastr.success('Imported successfully');
toastr.warning('Some entries skipped', 'Import Warning', { timeOut: 5000 });
toastr.error('Import failed: ' + error.message);

Built upon SillyTavern