@gptp/core
Version:
Library to supercharge your use of large language models
914 lines (889 loc) • 113 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('n12'), require('spacetrim')) :
typeof define === 'function' && define.amd ? define(['exports', 'n12', 'spacetrim'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["ptp-core"] = {}, global.n12, global.spaceTrim));
})(this, (function (exports, n12, spaceTrim) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(spaceTrim);
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
/**
* The version of the PTP
*/
var PTP_VERSION = '0.1.0';
/**
* Default model requirements for the pipeline
*/
var DEFAULT_MODEL_REQUIREMENTS = {
variant: 'CHAT',
};
/**
* Supported script languages
*/
var SUPPORTED_SCRIPT_LANGUAGES = ['javascript', 'typescript', 'python'];
/**
* Computes the deepness of the markdown structure.
*
* @private within the library
*/
function countMarkdownStructureDeepness(markdownStructure) {
var e_1, _a;
var maxDeepness = 0;
try {
for (var _b = __values(markdownStructure.sections), _c = _b.next(); !_c.done; _c = _b.next()) {
var section = _c.value;
maxDeepness = Math.max(maxDeepness, countMarkdownStructureDeepness(section));
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
return maxDeepness + 1;
}
/**
* Parse a markdown string into a MarkdownStructure object.
*
* Note: This function does work with code blocks
* Note: This function does not work with markdown comments
*
* @param markdown The markdown string to parse.
* @returns The MarkdownStructure object.
*
* @private within the library
*/
function markdownToMarkdownStructure(markdown) {
var e_1, _a;
var lines = markdown.split('\n');
var root = { level: 0, title: '', contentLines: [], sections: [], parent: null };
var current = root;
var isInsideCodeBlock = false;
try {
for (var lines_1 = __values(lines), lines_1_1 = lines_1.next(); !lines_1_1.done; lines_1_1 = lines_1.next()) {
var line = lines_1_1.value;
var headingMatch = line.match(/^(?<mark>#{1,6})\s(?<title>.*)/);
if (isInsideCodeBlock || !headingMatch) {
if (line.startsWith('```')) {
isInsideCodeBlock = !isInsideCodeBlock;
}
current.contentLines.push(line);
}
else {
var level = headingMatch.groups.mark.length;
var title = headingMatch.groups.title.trim();
var parent_1 = void 0;
if (level > current.level) {
// Note: Going deeper (next section is child of current)
parent_1 = current;
}
else {
// Note: Going up or staying at the same level (next section is sibling or parent or grandparent,... of current)
parent_1 = current;
while (parent_1.level !== level - 1) {
if (parent_1.parent === null /* <- Note: We are in root */) {
throw new Error(spaceTrim__default["default"]("\n The file has an invalid structure.\n The markdown file must have exactly one top-level section.\n "));
}
parent_1 = parent_1.parent;
}
}
var section = { level: level, title: title, contentLines: [], sections: [], parent: parent_1 };
parent_1.sections.push(section);
current = section;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (lines_1_1 && !lines_1_1.done && (_a = lines_1.return)) _a.call(lines_1);
}
finally { if (e_1) throw e_1.error; }
}
if (root.sections.length === 1) {
var markdownStructure = parsingMarkdownStructureToMarkdownStructure(root.sections[0]);
return markdownStructure;
}
throw new Error('The markdown file must have exactly one top-level section.');
// return root;
}
/**
* @private
*/
function parsingMarkdownStructureToMarkdownStructure(parsingMarkdownStructure) {
var level = parsingMarkdownStructure.level, title = parsingMarkdownStructure.title, contentLines = parsingMarkdownStructure.contentLines, sections = parsingMarkdownStructure.sections;
return {
level: level,
title: title,
content: spaceTrim__default["default"](contentLines.join('\n')),
sections: sections.map(parsingMarkdownStructureToMarkdownStructure),
};
}
/**
* Utility function to extract all list items from markdown
*
* Note: It works with both ul and ol
* Note: It omits list items in code blocks
* Note: It flattens nested lists
* Note: It can not work with html syntax and comments
*
* @param markdown any valid markdown
* @returns
*
* @private within the library
*/
function extractAllListItemsFromMarkdown(markdown) {
var e_1, _a;
var lines = markdown.split('\n');
var listItems = [];
var isInCodeBlock = false;
try {
for (var lines_1 = __values(lines), lines_1_1 = lines_1.next(); !lines_1_1.done; lines_1_1 = lines_1.next()) {
var line = lines_1_1.value;
var trimmedLine = line.trim();
if (trimmedLine.startsWith('```')) {
isInCodeBlock = !isInCodeBlock;
}
if (!isInCodeBlock && (trimmedLine.startsWith('-') || trimmedLine.match(/^\d+\./))) {
var listItem = trimmedLine.replace(/^-|\d+\./, '').trim();
listItems.push(listItem);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (lines_1_1 && !lines_1_1.done && (_a = lines_1.return)) _a.call(lines_1);
}
finally { if (e_1) throw e_1.error; }
}
return listItems;
}
/**
* Extracts all code blocks from markdown.
*
* @param markdown any valid markdown
* @returns code blocks with language and content
*
* @private within the library
*/
function extractBlocksFromMarkdown(markdown) {
var e_1, _a;
var codeBlocks = [];
var lines = markdown.split('\n');
var currentLanguage = null;
var currentBlock = [];
try {
for (var lines_1 = __values(lines), lines_1_1 = lines_1.next(); !lines_1_1.done; lines_1_1 = lines_1.next()) {
var line = lines_1_1.value;
if (line.startsWith('```')) {
if (currentLanguage !== null) {
codeBlocks.push({ language: currentLanguage.trim() || null, content: currentBlock.join('\n') });
currentLanguage = null;
currentBlock = [];
}
else {
currentLanguage = line.slice(3).trim();
}
}
else if (currentLanguage !== null) {
currentBlock.push(line);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (lines_1_1 && !lines_1_1.done && (_a = lines_1.return)) _a.call(lines_1);
}
finally { if (e_1) throw e_1.error; }
}
if (currentLanguage !== null) {
codeBlocks.push({ language: currentLanguage.trim() || null, content: currentBlock.join('\n') });
}
return codeBlocks;
}
/**
* Extracts exactly ONE code block from markdown.
*
* Note: This function is similar to extractBlocksFromMarkdown but it validates that there is exactly one code block.
* Note: If there are multiple or no code blocks the function throws an error
*
* @param markdown any valid markdown
* @returns code block with language and content
*
* @private within the library
*/
function extractOneBlockFromMarkdown(markdown) {
var codeBlocks = extractBlocksFromMarkdown(markdown);
if (codeBlocks.length !== 1) {
throw new Error('There should be exactly one code block in the markdown');
}
return codeBlocks[0];
}
/**
* Removes HTML or Markdown comments from a string.
*
* @param {string} content - The string to remove comments from.
* @returns {string} The input string with all comments removed.
*/
function removeContentComments(content) {
return spaceTrim__default["default"](content.replace(/<!--(.*?)-->/gs, ''));
}
/**
* Execution type describes the way how the block is executed
*
* @see https://github.com/webgptorg/ptp#execution-type
*/
var ExecutionTypes = ['PROMPT_TEMPLATE', 'SIMPLE_TEMPLATE', 'SCRIPT', 'PROMPT_DIALOG'];
/**
* Removes Markdown formatting tags from a string.
*
* @param {string} str - The string to remove Markdown tags from.
* @returns {string} The input string with all Markdown tags removed.
*/
function removeMarkdownFormatting(str) {
// Remove bold formatting
str = str.replace(/\*\*(.*?)\*\*/g, '$1');
// Remove italic formatting
str = str.replace(/\*(.*?)\*/g, '$1');
// Remove code formatting
str = str.replace(/`(.*?)`/g, '$1');
return str;
}
/**
* Parses one line of ul/ol to command
*/
function parseCommand(listItem) {
if (listItem.includes('\n') || listItem.includes('\r')) {
throw new Error('Command can not contain new line characters:');
}
var type = listItem.trim();
type = type.split('`').join('');
type = type.split('"').join('');
type = type.split("'").join('');
type = type.split('~').join('');
type = type.split('[').join('');
type = type.split(']').join('');
type = type.split('(').join('');
type = type.split(')').join('');
type = n12.normalizeTo_SCREAMING_CASE(type);
type = type.split('DIALOGUE').join('DIALOG');
var listItemParts = listItem
.split(' ')
.map(function (part) { return part.trim(); })
.filter(function (item) { return item !== ''; })
.filter(function (item) { return !/^PTP$/i.test(item); })
.map(removeMarkdownFormatting);
if (type.startsWith('URL') || type.startsWith('PTP_URL') || type.startsWith('PTPURL') || type.startsWith('HTTPS')) {
if (!(listItemParts.length === 2 || (listItemParts.length === 1 && type.startsWith('HTTPS')))) {
throw new Error(spaceTrim__default["default"]("\n Invalid PTP_URL command:\n\n - ".concat(listItem, "\n ")));
}
var ptpUrlString = listItemParts.pop();
var ptpUrl = new URL(ptpUrlString);
if (ptpUrl.protocol !== 'https:') {
throw new Error(spaceTrim__default["default"]("\n Invalid PTP_URL command:\n\n - ".concat(listItem, "\n\n Protocol must be HTTPS\n ")));
}
if (ptpUrl.hash !== '') {
throw new Error(spaceTrim__default["default"]("\n Invalid PTP_URL command:\n\n - ".concat(listItem, "\n\n URL must not contain hash\n Hash is used for identification of the prompt template in the pipeline\n ")));
}
return {
type: 'PTP_URL',
ptpUrl: ptpUrl,
};
}
else if (type.startsWith('PTP_VERSION')) {
if (listItemParts.length !== 2) {
throw new Error(spaceTrim__default["default"]("\n Invalid PTP_VERSION command:\n\n - ".concat(listItem, "\n ")));
}
var ptpVersion = listItemParts.pop();
// TODO: Validate version
return {
type: 'PTP_VERSION',
ptpVersion: ptpVersion,
};
}
else if (type.startsWith('EXECUTE') ||
type.startsWith('EXEC') ||
type.startsWith('PROMPT_DIALOG') ||
type.startsWith('SIMPLE_TEMPLATE')) {
var executionTypes = ExecutionTypes.filter(function (executionType) { return type.includes(executionType); });
if (executionTypes.length !== 1) {
throw new Error(spaceTrim__default["default"](function (block) { return "\n Unknown execution type in command:\n\n - ".concat(listItem, "\n\n Supported execution types are:\n ").concat(block(ExecutionTypes.join(', ')), "\n "); }));
}
return {
type: 'EXECUTE',
executionType: executionTypes[0],
};
}
else if (type.startsWith('USE')) {
// TODO: Make this more elegant and dynamically
if (type.includes('CHAT')) {
return {
type: 'USE',
key: 'variant',
value: 'CHAT',
};
}
else if (type.includes('COMPLETION')) {
return {
type: 'USE',
key: 'variant',
value: 'COMPLETION',
};
}
else {
throw new Error(spaceTrim__default["default"](function (block) { return "\n Unknown variant in command:\n\n - ".concat(listItem, "\n\n Supported variants are:\n ").concat(block(['CHAT', 'COMPLETION'].join(', ')), "\n "); }));
}
}
else if (type.startsWith('PARAM') ||
type.startsWith('INPUT_PARAM') ||
type.startsWith('OUTPUT_PARAM') ||
listItem.startsWith('{') ||
listItem.startsWith('> {') /* <- Note: This is a bit hack to parse return parameters defined at the end of each section */) {
var parametersMatch = listItem.match(/\{(?<parameterName>[a-z0-9_]+)\}[^\S\r\n]*(?<parameterDescription>.*)$/im);
if (!parametersMatch || !parametersMatch.groups || !parametersMatch.groups.parameterName) {
throw new Error(spaceTrim__default["default"]("\n Invalid parameter in command:\n\n - ".concat(listItem, "\n ")));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var _a = parametersMatch.groups, parameterName = _a.parameterName, parameterDescription = _a.parameterDescription;
if (parameterDescription && parameterDescription.match(/\{(?<parameterName>[a-z0-9_]+)\}/im)) {
throw new Error(spaceTrim__default["default"]("\n Parameter {".concat(parameterName, "} can not contain another parameter in description:\n\n - ").concat(listItem, "\n ")));
}
var isInputParameter = type.startsWith('INPUT');
return {
type: 'PARAMETER',
parameterName: parameterName,
parameterDescription: parameterDescription.trim() || null,
isInputParameter: isInputParameter,
};
}
else if (type.startsWith('POSTPROCESS') || type.startsWith('POST_PROCESS')) {
if (listItemParts.length !== 2) {
throw new Error(spaceTrim__default["default"]("\n Invalid POSTPROCESSING command:\n\n - ".concat(listItem, "\n ")));
}
var functionName = listItemParts.pop();
return {
type: 'POSTPROCESS',
functionName: functionName,
};
}
else {
throw new Error(spaceTrim__default["default"]("\n Unknown command:\n\n - ".concat(listItem, "\n\n Supported commands are:\n - Execute\n - Use\n - Parameter\n - Input parameter\n - Output parameter\n - PTP Version\n ")));
}
}
/**
* Parse prompt template pipeline from string format to JSON format
*
* Note: This function does not validate logic of the pipeline only the syntax
*/
function promptTemplatePipelineStringToJson(promptTemplatePipelineString) {
var e_1, _a, e_2, _b;
var ptpJson = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
title: undefined /* <- Note: Putting here placeholder to keep `title` on top at final JSON */,
ptpUrl: undefined /* <- Note: Putting here placeholder to keep `ptpUrl` on top at final JSON */,
ptpVersion: PTP_VERSION,
description: undefined /* <- Note: Putting here placeholder to keep `description` on top at final JSON */,
parameters: [],
promptTemplates: [],
};
// =============================================================
// Note: 1️⃣ Normalization of the PTP string
promptTemplatePipelineString = removeContentComments(promptTemplatePipelineString);
promptTemplatePipelineString = promptTemplatePipelineString.replaceAll(/`\{(?<paramName>[a-z0-9_]+)\}`/gi, '{$<paramName>}');
promptTemplatePipelineString = promptTemplatePipelineString.replaceAll(/`->\s+\{(?<paramName>[a-z0-9_]+)\}`/gi, '-> {$<paramName>}');
// =============================================================
///Note: 2️⃣ Function for adding parameters
var addParam = function (parameterCommand) {
var parameterName = parameterCommand.parameterName, parameterDescription = parameterCommand.parameterDescription, isInputParameter = parameterCommand.isInputParameter;
var existingParameter = ptpJson.parameters.find(function (parameter) { return parameter.name === parameterName; });
if (existingParameter &&
existingParameter.description &&
existingParameter.description !== parameterDescription &&
parameterDescription) {
throw new Error(spaceTrim__default["default"](function (block) { return "\n Parameter {".concat(parameterName, "} is defined multiple times with different description.\n\n First definition:\n ").concat(block(existingParameter.description || '[undefined]'), "\n\n Second definition:\n ").concat(block(parameterDescription || '[undefined]'), "\n "); }));
}
if (existingParameter) {
if (parameterDescription) {
existingParameter.description = parameterDescription;
}
}
else {
ptpJson.parameters.push({
name: parameterName,
description: parameterDescription || undefined,
isInput: isInputParameter,
});
}
};
// =============================================================
// Note: 3️⃣ Parse the dynamic part - the template pipeline
var markdownStructure = markdownToMarkdownStructure(promptTemplatePipelineString);
var markdownStructureDeepness = countMarkdownStructureDeepness(markdownStructure);
if (markdownStructureDeepness !== 2) {
throw new Error(spaceTrim__default["default"]("\n Invalid markdown structure.\n The markdown must have exactly 2 levels of headings (one top-level section and one section for each template).\n Now it has ".concat(markdownStructureDeepness, " levels of headings.\n ")));
}
ptpJson.title = markdownStructure.title;
// TODO: [1] DRY description
var description = markdownStructure.content;
// Note: Remove codeblocks
description = description.split(/^```.*^```/gms).join('');
//Note: Remove lists and return statement
description = description.split(/^(?:(?:-)|(?:\d\))|(?:`?->))\s+.*$/gm).join('');
description = spaceTrim__default["default"](description);
if (description === '') {
description = undefined;
}
ptpJson.description = description;
var defaultModelRequirements = __assign({}, DEFAULT_MODEL_REQUIREMENTS);
var listItems = extractAllListItemsFromMarkdown(markdownStructure.content);
try {
for (var listItems_1 = __values(listItems), listItems_1_1 = listItems_1.next(); !listItems_1_1.done; listItems_1_1 = listItems_1.next()) {
var listItem = listItems_1_1.value;
var command = parseCommand(listItem);
switch (command.type) {
case 'PTP_URL':
ptpJson.ptpUrl = command.ptpUrl.href;
break;
case 'PTP_VERSION':
ptpJson.ptpVersion = command.ptpVersion;
break;
case 'USE':
defaultModelRequirements[command.key] = command.value;
break;
case 'PARAMETER':
addParam(command);
break;
default:
throw new Error("Command ".concat(command.type, " is not allowed in the head of the prompt template pipeline ONLY at the prompt template block"));
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (listItems_1_1 && !listItems_1_1.done && (_a = listItems_1.return)) _a.call(listItems_1);
}
finally { if (e_1) throw e_1.error; }
}
var _loop_1 = function (section) {
var e_3, _e, e_4, _f;
// TODO: Parse prompt template description (the content out of the codeblock and lists)
var templateModelRequirements = __assign({}, defaultModelRequirements);
var postprocessingCommands = [];
var listItems_3 = extractAllListItemsFromMarkdown(section.content);
var executionType = 'PROMPT_TEMPLATE';
var isExecutionTypeChanged = false;
try {
for (var listItems_2 = (e_3 = void 0, __values(listItems_3)), listItems_2_1 = listItems_2.next(); !listItems_2_1.done; listItems_2_1 = listItems_2.next()) {
var listItem = listItems_2_1.value;
var command = parseCommand(listItem);
switch (command.type) {
case 'EXECUTE':
if (isExecutionTypeChanged) {
throw new Error('Execution type is already defined in the prompt template. It can be defined only once.');
}
executionType = command.executionType;
isExecutionTypeChanged = true;
break;
case 'USE':
templateModelRequirements[command.key] = command.value;
break;
case 'PARAMETER':
addParam(command);
break;
case 'POSTPROCESS':
postprocessingCommands.push(command);
break;
default:
throw new Error("Command ".concat(command.type, " is not allowed in the block of the prompt template ONLY at the head of the prompt template pipeline"));
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (listItems_2_1 && !listItems_2_1.done && (_e = listItems_2.return)) _e.call(listItems_2);
}
finally { if (e_3) throw e_3.error; }
}
var _g = extractOneBlockFromMarkdown(section.content), language = _g.language, content = _g.content;
if (executionType === 'SCRIPT') {
if (!language) {
throw new Error('You must specify the language of the script in the prompt template');
}
else if (!SUPPORTED_SCRIPT_LANGUAGES.includes(language)) {
throw new Error(spaceTrim__default["default"](function (block) { return "\n Script language ".concat(language, " is not supported.\n\n Supported languages are:\n ").concat(block(SUPPORTED_SCRIPT_LANGUAGES.join(', ')), "\n\n "); }));
}
}
var lastLine = section.content.split('\n').pop();
var match = /^->\s*\{(?<resultingParamName>[a-z0-9_]+)\}/im.exec(lastLine);
if (!match || match.groups === undefined || match.groups.resultingParamName === undefined) {
throw new Error(spaceTrim__default["default"](function (block) { return "\n Invalid template - each section must end with \"-> {...}\"\n\n Invalid section:\n ".concat(block(
// TODO: Show code of invalid sections each time + DRY
section.content
.split('\n')
.map(function (line) { return "> ".concat(line); })
.join('\n')), "\n "); }));
}
var resultingParameterName = match.groups.resultingParamName;
// TODO: [1] DRY description
var description_1 = section.content;
// Note: Remove codeblocks
description_1 = description_1.split(/^```.*^```/gms).join('');
//Note: Remove lists and return statement
description_1 = description_1.split(/^(?:(?:-)|(?:\d\))|(?:`?->))\s+.*$/gm).join('');
description_1 = spaceTrim__default["default"](description_1);
if (description_1 === '') {
description_1 = undefined;
}
var getParameterName = function (i) {
var parameterName = postprocessingCommands.length <= i
? resultingParameterName
: n12.normalizeTo_camelCase("".concat(resultingParameterName, " before ").concat(postprocessingCommands[i].functionName));
var isParameterDefined = ptpJson.parameters.some(function (parameter) { return parameter.name === parameterName; });
if (!isParameterDefined) {
var parameterDescription = "*(".concat(n12.capitalize(section.title), " postprocessing ").concat(i + 1, "/").concat(postprocessingCommands.length, ")* {").concat(resultingParameterName, "} before `").concat(postprocessingCommands[i].functionName, "`");
addParam({
parameterName: parameterName,
parameterDescription: parameterDescription,
isInputParameter: false,
// TODO:> isPrivate: true,
});
}
return parameterName;
};
ptpJson.promptTemplates.push({
name: n12.normalizeTo_PascalCase(section.title),
title: section.title,
description: description_1,
executionType: executionType,
modelRequirements: templateModelRequirements,
contentLanguage: executionType === 'SCRIPT' ? language : undefined,
content: content,
resultingParameterName: getParameterName(0),
});
try {
for (var _h = (e_4 = void 0, __values(postprocessingCommands.map(function (_a, i) {
var functionName = _a.functionName;
return [functionName, i];
}))), _j = _h.next(); !_j.done; _j = _h.next()) {
var _k = __read(_j.value, 2), functionName = _k[0], i = _k[1];
ptpJson.promptTemplates.push({
name: n12.normalizeTo_PascalCase(section.title + ' Postprocessing ' + i),
title: "(".concat(i + 1, "/").concat(postprocessingCommands.length, ") ").concat(section.title, " postprocessing"),
description: "Postprocessing of section ".concat(section.title, " finally with resulting parameter {").concat(resultingParameterName, "}"),
executionType: 'SCRIPT',
contentLanguage: 'javascript',
content: "".concat(functionName, "(").concat(getParameterName(i), ")"),
resultingParameterName: getParameterName(i + 1),
});
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (_j && !_j.done && (_f = _h.return)) _f.call(_h);
}
finally { if (e_4) throw e_4.error; }
}
};
try {
for (var _c = __values(markdownStructure.sections), _d = _c.next(); !_d.done; _d = _c.next()) {
var section = _d.value;
_loop_1(section);
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
}
finally { if (e_2) throw e_2.error; }
}
// =============================================================
return ptpJson;
}
/**
* TODO: Report here line/column of error
* TODO: Use spaceTrim more effectively
*/
/**
* Validates PromptTemplatePipelineJson if it is logically valid.
*
* It checks:
* - if it has correct parameters dependency
*
* It does NOT check:
* - if it is valid json
* - if it is meaningful
*
* @param ptp valid or invalid PromptTemplatePipelineJson
* @throws {Error} if invalid
*/
function validatePromptTemplatePipelineJson(ptp) {
var e_1, _a, e_2, _b;
var definedParameters = new Set(ptp.parameters.filter(function (_a) {
var isInput = _a.isInput;
return isInput;
}).map(function (_a) {
var name = _a.name;
return name;
}));
try {
for (var _c = __values(ptp.promptTemplates), _d = _c.next(); !_d.done; _d = _c.next()) {
var template = _d.value;
try {
for (var _e = (e_2 = void 0, __values(Array.from(template.content.matchAll(/\{(?<parameterName>[a-z0-9_]+)\}/gi)))), _f = _e.next(); !_f.done; _f = _e.next()) {
var match = _f.value;
var parameterName = match.groups.parameterName;
if (!definedParameters.has(parameterName)) {
throw new Error("Parameter {".concat(parameterName, "} used before defined"));
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
}
finally { if (e_2) throw e_2.error; }
}
if (definedParameters.has(template.resultingParameterName)) {
throw new Error("Parameter {".concat(template.resultingParameterName, "} is defined multiple times"));
}
definedParameters.add(template.resultingParameterName);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
}
finally { if (e_1) throw e_1.error; }
}
}
/**
* TODO: [🧠] Work with ptpVersion
* TODO: Use here some json-schema, Zod or something similar and change it to:
* > /**
* > * Validates PromptTemplatePipelineJson if it is logically valid.
* > *
* > * It checks:
* > * - it has a valid structure
* > * - ...
* > ex port function validatePromptTemplatePipelineJson(ptp: unknown): asserts ptp is PromptTemplatePipelineJson {
*/
/**
* Prompt template pipeline is the **core concept of this library**.
* It represents a series of prompt templates chained together to form a pipeline / one big prompt template with input and result parameters.
*
* It can have 3 formats:
* - **.ptp.md file** in custom markdown format described above
* - **JSON** format, parsed from the .ptp.md file
* - _(this)_ **Object** which is created from JSON format and bound with tools around (but not the execution logic)
*
* @see https://github.com/webgptorg/ptp#prompt-template-pipeline
*/
var PromptTemplatePipeline = /** @class */ (function () {
function PromptTemplatePipeline(ptpUrl, parameters, promptTemplates) {
this.ptpUrl = ptpUrl;
this.parameters = parameters;
this.promptTemplates = promptTemplates;
if (promptTemplates.length === 0) {
throw new Error('Prompt template pipeline must have at least one prompt template');
}
}
/**
* Constructs PromptTemplatePipeline from any source
*
* Note: During the construction syntax and logic of source is validated
*
* @param source content of .ptp.md or .ptp.json file
* @returns PromptTemplatePipeline
*/
PromptTemplatePipeline.fromSource = function (ptpSource) {
if (typeof ptpSource === 'string') {
return PromptTemplatePipeline.fromString(ptpSource);
}
else {
return PromptTemplatePipeline.fromJson(ptpSource);
}
};
/**
* Constructs PromptTemplatePipeline from markdown source
*
* Note: During the construction syntax and logic of source is validated
*
* @param ptpString content of .ptp.md file
* @returns PromptTemplatePipeline
*/
PromptTemplatePipeline.fromString = function (ptpString) {
var ptpjson = promptTemplatePipelineStringToJson(ptpString);
return PromptTemplatePipeline.fromJson(ptpjson);
};
/**
* Constructs PromptTemplatePipeline from JSON source
*
* Note: During the construction the source is logic validated
*
* @param ptpjson content of .ptp.json file parsed into JSON
* @returns PromptTemplatePipeline
*/
PromptTemplatePipeline.fromJson = function (ptpjson) {
validatePromptTemplatePipelineJson(ptpjson);
return new PromptTemplatePipeline(ptpjson.ptpUrl ? new URL(ptpjson.ptpUrl) : null, Object.fromEntries(ptpjson.parameters.map(function (parameter) { return [parameter.name, parameter]; })), ptpjson.promptTemplates);
};
Object.defineProperty(PromptTemplatePipeline.prototype, "entryPromptTemplate", {
/**
* Returns the first prompt template in the pipeline
*/
get: function () {
return this.promptTemplates[0];
},
enumerable: false,
configurable: true
});
/**
* Gets the parameter that is the result of given prompt template
*/
PromptTemplatePipeline.prototype.getResultingParameter = function (promptTemplateName) {
var index = this.promptTemplates.findIndex(function (_a) {
var name = _a.name;
return name === promptTemplateName;
});
if (index === -1) {
throw new Error('Prompt template is not in this pipeline');
}
var resultingParameterName = this.promptTemplates[index].resultingParameterName;
var resultingParameter = this.parameters[resultingParameterName];
if (!resultingParameter) {
// <- TODO: [🥨] Make some NeverShouldHappenError
throw new Error("Resulting parameter of prompt template ".concat(promptTemplateName, " {").concat(resultingParameterName, "} is not defined"));
}
return resultingParameter;
};
/**
* Gets the following prompt template in the pipeline or null if there is no following prompt template and this is the last one
*/
PromptTemplatePipeline.prototype.getFollowingPromptTemplate = function (promptTemplateName) {
var index = this.promptTemplates.findIndex(function (_a) {
var name = _a.name;
return name === promptTemplateName;
});
if (index === -1) {
throw new Error('Prompt template is not in this pipeline');
}
if (index === this.promptTemplates.length - 1) {
return null;
}
return this.promptTemplates[index + 1];
};
return PromptTemplatePipeline;
}());
/**
* TODO: !! [👐][🧠] Split of PromptTemplatePipeline,PromptTemplatePipelineLibrary between interface and class
* TODO: !! [👐] Make parameters and promptTemplates private WHEN split between interface and class
* TODO: !! Add generic type for entry and result parameters
* TODO: Can be Array elegantly typed such as it must have at least one element?
* TODO: [🧠] Each PromptTemplatePipeline should have its unique hash to be able to compare them and execute on server ONLY the desired ones
*/
/**
* All possible emoji chars like "🍆", "🍡", "🍤"...
* Note: this will be needed to update annually - now updated at 2022-01-19
*
* @see https://getemoji.com/
*/
var EMOJIS_IN_CATEGORIES = {
Smileys: '😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳 😏 😒 😞 😔 😟 😕 🙁 ☹️ 😣 😖 😫 😩 🥺 😢 😭 😤 😠 😡 🤬 🤯 😳 🥵 🥶 😱 😨 😰 😥 😓 🤗 🤔 🤭 🤫 🤥 😶 😐 😑 😬 🙄 😯 😦 😧 😮 😲 🥱 😴 🤤 😪 😵 🤐 🥴 🤢 🤮 🤧 😷 🤒 🤕 🤑 🤠 😈 👿 👹 👺 🤡 💩 👻 💀 ☠️ 👽 👾 🤖 🎃 😺 😸 😹 😻 😼 😽 🙀 😿 😾'.split(' '),
'Gestures and Body Parts': '👋 🤚 🖐 ✋ 🖖 👌 🤌 🤏 ✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍 👎 ✊ 👊 🤛 🤜 👏 🙌 👐 🤲 🤝 🙏 ✍️ 💅 🤳 💪 🦾 🦵 🦿 🦶 👣 👂 🦻 👃 🫀 🫁 🧠 🦷 🦴 👀 👁 👅 👄 💋 🩸'.split(' '),
'People and Fantasy': '👶 👧 🧒 👦 👩 🧑 👨 👩🦱 🧑🦱 👨🦱 👩🦰 🧑🦰 👨🦰 👱♀️ 👱 👱♂️ 👩🦳 🧑🦳 👨🦳 👩🦲 🧑🦲 👨🦲 🧔 👵 🧓 👴 👲 👳♀️ 👳 👳♂️ 🧕 👮♀️ 👮 👮♂️ 👷♀️ 👷 👷♂️ 💂♀️ 💂 💂♂️ 🕵️♀️ 🕵️ 🕵️♂️ 👩⚕️ 🧑⚕️ 👨⚕️ 👩🌾 🧑🌾 👨🌾 👩🍳 🧑🍳 👨🍳 👩🎓 🧑🎓 👨🎓 👩🎤 🧑🎤 👨🎤 👩🏫 🧑🏫 👨🏫 👩🏭 🧑🏭 👨🏭 👩💻 🧑💻 👨💻 👩💼 🧑💼 👨💼 👩🔧 🧑🔧 👨🔧 👩🔬 🧑🔬 👨🔬 👩🎨 🧑🎨 👨🎨 👩🚒 🧑🚒 👨🚒 👩✈️ 🧑✈️ 👨✈️ 👩🚀 🧑🚀 👨🚀 👩⚖️ 🧑⚖️ 👨⚖️ 👰♀️ 👰 👰♂️ 🤵♀️ 🤵 🤵♂️ 👸 🤴 🥷 🦸♀️ 🦸 🦸♂️ 🦹♀️ 🦹 🦹♂️ 🤶 🧑🎄 🎅 🧙♀️ 🧙 🧙♂️ 🧝♀️ 🧝 🧝♂️ 🧛♀️ 🧛 🧛♂️ 🧟♀️ 🧟 🧟♂️ 🧞♀️ 🧞 🧞♂️ 🧜♀️ 🧜 🧜♂️ 🧚♀️ 🧚 🧚♂️ 👼 🤰 🤱 👩🍼 🧑🍼 👨🍼 🙇♀️ 🙇 🙇♂️ 💁♀️ 💁 💁♂️ 🙅♀️ 🙅 🙅♂️ 🙆♀️ 🙆 🙆♂️ 🙋♀️ 🙋 🙋♂️ 🧏♀️ 🧏 🧏♂️ 🤦♀️ 🤦 🤦♂️ 🤷♀️ 🤷 🤷♂️ 🙎♀️ 🙎 🙎♂️ 🙍♀️ 🙍 🙍♂️ 💇♀️ 💇 💇♂️ 💆♀️ 💆 💆♂️ 🧖♀️ 🧖 🧖♂️ 💅 🤳 💃 🕺 👯♀️ 👯 👯♂️ 🕴 👩🦽 🧑🦽 👨🦽 👩🦼 🧑🦼 👨🦼 🚶♀️ 🚶 🚶♂️ 👩🦯 🧑🦯 👨🦯 🧎♀️ 🧎 🧎♂️ 🏃♀️ 🏃 🏃♂️ 🧍♀️ 🧍 🧍♂️ 👭 🧑🤝🧑 👬 👫 👩❤️👩 💑 👨❤️👨 👩❤️👨 👩❤️💋👩 💏 👨❤️💋👨 👩❤️💋👨 👪 👨👩👦 👨👩👧 👨👩👧👦 👨👩👦👦 👨👩👧👧 👨👨👦 👨👨👧 👨👨👧👦 👨👨👦👦 👨👨👧👧 👩👩👦 👩👩👧 👩👩👧👦 👩👩👦👦 👩👩👧👧 👨👦 👨👦👦 👨👧 👨👧👦 👨👧👧 👩👦 👩👦👦 👩👧 👩👧👦 👩👧👧 🗣 👤 👥 🫂'.split(' '),
'Clothing and Accessories': '🧳 🌂 ☂️ 🧵 🪡 🪢 🧶 👓 🕶 🥽 🥼 🦺 👔 👕 👖 🧣 🧤 🧥 🧦 👗 👘 🥻 🩴 🩱 🩲 🩳 👙 👚 👛 👜 👝 🎒 👞 👟 🥾 🥿 👠 👡 🩰 👢 👑 👒 🎩 🎓 🧢 ⛑ 🪖 💄 💍 💼'.split(' '),
'Pale Emojis': '👋🏻 🤚🏻 🖐🏻 ✋🏻 🖖🏻 👌🏻 🤌🏻 🤏🏻 ✌🏻 🤞🏻 🤟🏻 🤘🏻 🤙🏻 👈🏻 👉🏻 👆🏻 🖕🏻 👇🏻 ☝🏻 👍🏻 👎🏻 ✊🏻 👊🏻 🤛🏻 🤜🏻 👏🏻 🙌🏻 👐🏻 🤲🏻 🙏🏻 ✍🏻 💅🏻 🤳🏻 💪🏻 🦵🏻 🦶🏻 👂🏻 🦻🏻 👃🏻 👶🏻 👧🏻 🧒🏻 👦🏻 👩🏻 🧑🏻 👨🏻 👩🏻🦱 🧑🏻🦱 👨🏻🦱 👩🏻🦰 🧑🏻🦰 👨🏻🦰 👱🏻♀️ 👱🏻 👱🏻♂️ 👩🏻🦳 🧑🏻🦳 👨🏻🦳 👩🏻🦲 🧑🏻🦲 👨🏻🦲 🧔🏻 👵🏻 🧓🏻 👴🏻 👲🏻 👳🏻♀️ 👳🏻 👳🏻♂️ 🧕🏻 👮🏻♀️ 👮🏻 👮🏻♂️ 👷🏻♀️ 👷🏻 👷🏻♂️ 💂🏻♀️ 💂🏻 💂🏻♂️ 🕵🏻♀️ 🕵🏻 🕵🏻♂️ 👩🏻⚕️ 🧑🏻⚕️ 👨🏻⚕️ 👩🏻🌾 🧑🏻🌾 👨🏻🌾 👩🏻🍳 🧑🏻🍳 👨🏻🍳 👩🏻🎓 🧑🏻🎓 👨🏻🎓 👩🏻🎤 🧑🏻🎤 👨🏻🎤 👩🏻🏫 🧑🏻🏫 👨🏻🏫 👩🏻🏭 🧑🏻🏭 👨🏻🏭 👩🏻💻 🧑🏻💻 👨🏻💻 👩🏻💼 🧑🏻💼 👨🏻💼 👩🏻🔧 🧑🏻🔧 👨🏻🔧 👩🏻🔬 🧑🏻🔬 👨🏻🔬 👩🏻🎨 🧑🏻🎨 👨🏻🎨 👩🏻🚒 🧑🏻🚒 👨🏻🚒 👩🏻✈️ 🧑🏻✈️ 👨🏻✈️ 👩🏻🚀 🧑🏻🚀 👨🏻🚀 👩🏻⚖️ 🧑🏻⚖️ 👨🏻⚖️ 👰🏻♀️ 👰🏻 👰🏻♂️ 🤵🏻♀️ 🤵🏻 🤵🏻♂️ 👸🏻 🤴🏻 🥷🏻 🦸🏻♀️ 🦸🏻 🦸🏻♂️ 🦹🏻♀️ 🦹🏻 🦹🏻♂️ 🤶🏻 🧑🏻🎄 🎅🏻 🧙🏻♀️ 🧙🏻 🧙🏻♂️ 🧝🏻♀️ 🧝🏻 🧝🏻♂️ 🧛🏻♀️ 🧛🏻 🧛🏻♂️ 🧜🏻♀️ 🧜🏻 🧜🏻♂️ 🧚🏻♀️ 🧚🏻 🧚🏻♂️ 👼🏻 🤰🏻 🤱🏻 👩🏻🍼 🧑🏻🍼 👨🏻🍼 🙇🏻♀️ 🙇🏻 🙇🏻♂️ 💁🏻♀️ 💁🏻 💁🏻♂️ 🙅🏻♀️ 🙅🏻 🙅🏻♂️ 🙆🏻♀️ 🙆🏻 🙆🏻♂️ 🙋🏻♀️ 🙋🏻 🙋🏻♂️ 🧏🏻♀️ 🧏🏻 🧏🏻♂️ 🤦🏻♀️ 🤦🏻 🤦🏻♂️ 🤷🏻♀️ 🤷🏻 🤷🏻♂️ 🙎🏻♀️ 🙎🏻 🙎🏻♂️ 🙍🏻♀️ 🙍🏻 🙍🏻♂️ 💇🏻♀️ 💇🏻 💇🏻♂️ 💆🏻♀️ 💆🏻 💆🏻♂️ 🧖🏻♀️ 🧖🏻 🧖🏻♂️ 💃🏻 🕺🏻 🕴🏻 👩🏻🦽 🧑🏻🦽 👨🏻🦽 👩🏻🦼 🧑🏻🦼 👨🏻🦼 🚶🏻♀️ 🚶🏻 🚶🏻♂️ 👩🏻🦯 🧑🏻🦯 👨🏻🦯 🧎🏻♀️ 🧎🏻 🧎🏻♂️ 🏃🏻♀️ 🏃🏻 🏃🏻♂️ 🧍🏻♀️ 🧍🏻 🧍🏻♂️ 👭🏻 🧑🏻🤝🧑🏻 👬🏻 👫🏻 🧗🏻♀️ 🧗🏻 🧗🏻♂️ 🏇🏻 🏂🏻 🏌🏻♀️ 🏌🏻 🏌🏻♂️ 🏄🏻♀️ 🏄🏻 🏄🏻♂️ 🚣🏻♀️ 🚣🏻 🚣🏻♂️ 🏊🏻♀️ 🏊🏻 🏊🏻♂️ ⛹🏻♀️ ⛹🏻 ⛹🏻♂️ 🏋🏻♀️ 🏋🏻 🏋🏻♂️ 🚴🏻♀️ 🚴🏻 🚴🏻♂️ 🚵🏻♀️ 🚵🏻 🚵🏻♂️ 🤸🏻♀️ 🤸🏻 🤸🏻♂️ 🤽🏻♀️ 🤽🏻 🤽🏻♂️ 🤾🏻♀️ 🤾🏻 🤾🏻♂️ 🤹🏻♀️ 🤹🏻 🤹🏻♂️ 🧘🏻♀️ 🧘🏻 🧘🏻♂️ 🛀🏻 🛌'.split(' '),
'Cream White Emojis': '👋🏼 🤚🏼 🖐🏼 ✋🏼 🖖🏼 👌🏼 🤌🏼 🤏🏼 ✌🏼 🤞🏼 🤟🏼 🤘🏼 🤙🏼 👈🏼 👉🏼 👆🏼 🖕🏼 👇🏼 ☝🏼 👍🏼 👎🏼 ✊🏼 👊🏼 🤛🏼 🤜🏼 👏🏼 🙌🏼 👐🏼 🤲🏼 🙏🏼 ✍🏼 💅🏼 🤳🏼 💪🏼 🦵🏼 🦶🏼 👂🏼 🦻🏼 👃🏼 👶🏼 👧🏼 🧒🏼 👦🏼 👩🏼 🧑🏼 👨🏼 👩🏼🦱 🧑🏼🦱 👨🏼🦱 👩🏼🦰 🧑🏼🦰 👨🏼🦰 👱🏼♀️ 👱🏼 👱🏼♂️ 👩🏼🦳 🧑🏼🦳 👨🏼🦳 👩🏼🦲 🧑🏼🦲 👨🏼🦲 🧔🏼 👵🏼 🧓🏼 👴🏼 👲🏼 👳🏼♀️ 👳🏼 👳🏼♂️ 🧕🏼 👮🏼♀️ 👮🏼 👮🏼♂️ 👷🏼♀️ 👷🏼 👷🏼♂️ 💂🏼♀️ 💂🏼 💂🏼♂️ 🕵🏼♀️ 🕵🏼 🕵🏼♂️ 👩🏼⚕️ 🧑🏼⚕️ 👨🏼⚕️ 👩🏼🌾 🧑🏼🌾 👨🏼🌾 👩🏼🍳 🧑🏼🍳 👨🏼🍳 👩🏼🎓 🧑🏼🎓 👨🏼🎓 👩🏼🎤 🧑🏼🎤 👨🏼🎤 👩🏼🏫 🧑🏼🏫 👨🏼🏫 👩🏼🏭 🧑🏼🏭 👨🏼🏭 👩🏼💻 🧑🏼💻 👨🏼💻 👩🏼💼 🧑🏼💼 👨🏼💼 👩🏼🔧 🧑🏼🔧 👨🏼🔧 👩🏼🔬 🧑🏼🔬 👨🏼🔬 👩🏼🎨 🧑🏼🎨 👨🏼🎨 👩🏼🚒 🧑🏼🚒 👨🏼🚒 👩🏼✈️ 🧑🏼✈️ 👨🏼✈️ 👩🏼🚀 🧑🏼🚀 👨🏼🚀 👩🏼⚖️ 🧑🏼⚖️ 👨🏼⚖️ 👰🏼♀️ 👰🏼 👰🏼♂️ 🤵🏼♀️ 🤵🏼 🤵🏼♂️ 👸🏼 🤴🏼 🥷🏼 🦸🏼♀️ 🦸🏼 🦸🏼♂️ 🦹🏼♀️ 🦹🏼 🦹🏼♂️ 🤶🏼