botui
Version:
Build customizable conversational UIs and bots
161 lines (160 loc) • 6.24 kB
JavaScript
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;
};