UNPKG

botui

Version:

Build customizable conversational UIs and bots

161 lines (160 loc) 6.24 kB
import { createBlock, blockManager } from './block.js'; // even though the file has .ts extension, we need to use .js for resolution. import { resolveManager } from './resolve.js'; import { pluginManager } from './plugin.js'; import { actionManager } from './action.js'; export var BlockTypes; (function (BlockTypes) { BlockTypes["ACTION"] = "action"; BlockTypes["MESSAGE"] = "message"; })(BlockTypes = BlockTypes || (BlockTypes = {})); export const BOTUI_BLOCK_TYPES = BlockTypes; export const createBot = () => { const plugins = pluginManager(); const stateResolver = resolveManager(); const callbacks = { [BOTUI_BLOCK_TYPES.MESSAGE]: () => { }, [BOTUI_BLOCK_TYPES.ACTION]: () => { }, }; const doCallback = (state = '', data) => { const callback = callbacks[state]; callback(data); }; const blocks = blockManager((history) => { doCallback(BOTUI_BLOCK_TYPES.MESSAGE, history); }); const currentAction = actionManager((action) => { doCallback(BOTUI_BLOCK_TYPES.ACTION, action); }); const botuiInterface = { /** * Add, update or remove messages. */ message: { /** * Add a new non-action block to the chat list */ add: (data = { text: '' }, meta) => { return new Promise((resolve) => { stateResolver.set(resolve); const key = blocks.add(plugins.runWithPlugins(createBlock(BOTUI_BLOCK_TYPES.MESSAGE, meta, data))); stateResolver.resolve(key); }); }, /** * Get all of the current blocks listed in the chat. */ getAll: () => Promise.resolve(blocks.getAll()), /** * Load existing list of blocks */ setAll: (newBlocks) => { blocks.setAll(newBlocks); return Promise.resolve(blocks.getAll()); }, /** * Get a single block by it's key. */ get: (key = 0) => Promise.resolve(blocks.get(key)), /** * Remove a single block by it's key. */ remove: (key = 0) => { blocks.remove(key); return Promise.resolve(); }, /** * @param {BlockData} data an object with any values you want on the block * Update a single block by it's key. */ update: (key = 0, data = {}, meta) => { const existingBlock = blocks.get(key); const newMeta = meta ? Object.assign(Object.assign({}, existingBlock.meta), meta) : existingBlock.meta; const newData = data ? Object.assign(Object.assign({}, existingBlock.data), data) : existingBlock.data; blocks.update(key, plugins.runWithPlugins(createBlock(BOTUI_BLOCK_TYPES.MESSAGE, newMeta, newData, key))); return Promise.resolve(); }, /** * Removes all the blocks. */ removeAll: () => { blocks.clear(); return Promise.resolve(); }, }, action: { /** * Asks the user to perform an action. BotUI won't go further until * this action is resolved by calling `.next()` */ set: (data = {}, meta) => { return new Promise((resolve) => { const action = createBlock(BOTUI_BLOCK_TYPES.ACTION, meta, data); currentAction.set(action); stateResolver.set((resolvedData, resolvedMeta) => { currentAction.clear(); if (meta.ephemeral !== true) { // ephemeral = short-lived blocks.add(plugins.runWithPlugins(createBlock(BOTUI_BLOCK_TYPES.MESSAGE, Object.assign(Object.assign({}, resolvedMeta), { previous: action }), resolvedData))); } resolve(resolvedData); }); }); }, /** * Returns the current action or null if there is none. * @returns {Promise<Block>} */ get: () => { return Promise.resolve(currentAction.get()); }, }, /** * Wait does not let the next message/action resolve until .next() is called. * When `waitTime` property is present in the meta, .next() is called internally with that meta. */ wait: (waitOptions, forwardData, forwardMeta) => { const meta = { waiting: true, ephemeral: true, // to not add to the message history. see action.set for its usage. }; if (waitOptions === null || waitOptions === void 0 ? void 0 : waitOptions.waitTime) { setTimeout(() => botuiInterface.next(forwardData, forwardMeta), waitOptions.waitTime); } return botuiInterface.action.set({}, meta); }, /** * Add a listener for a BlockType. */ onChange: (state, cb) => { callbacks[state] = cb; return botuiInterface; }, /** * Resolves current action or wait command. Passed data is sent to the next .then() */ next: (...args) => { stateResolver.resolve(...args); return botuiInterface; }, /** * Register a plugin to manipulate block data. * Example: * The plugin below replaces `!(text)` with `<i>text</i>` ``` .use(block => { if (block.type == BOTUI_BLOCK_TYPES.MESSAGE) { block.data.text = block.data?.text?.replace(/!\(([^\)]+)\)/igm, "<i>$1</i>") } return block }) ``` */ use: (plugin) => { plugins.registerPlugin(plugin); return botuiInterface; }, }; return botuiInterface; };