UNPKG

@promptbook/vercel

Version:

Promptbook: Turn your company's scattered knowledge into AI ready books

1,349 lines (1,319 loc) โ€ข 84.9 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('spacetrim'), require('crypto')) : typeof define === 'function' && define.amd ? define(['exports', 'colors', 'spacetrim', 'crypto'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-vercel"] = {}, global.colors, global.spaceTrim, global.crypto)); })(this, (function (exports, colors, spaceTrim, crypto) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var colors__default = /*#__PURE__*/_interopDefaultLegacy(colors); var spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(spaceTrim); // โš ๏ธ WARNING: This code has been generated so that any manual changes will be overwritten /** * The version of the Book language * * @generated * @see https://github.com/webgptorg/book */ const BOOK_LANGUAGE_VERSION = '2.0.0'; /** * The version of the Promptbook engine * * @generated * @see https://github.com/webgptorg/promptbook */ const PROMPTBOOK_ENGINE_VERSION = '0.103.0-52'; /** * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name */ /** * Generates random token * * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic * Note: This function is cryptographically secure (it uses crypto.randomBytes internally) * * @private internal helper function * @returns secure random token */ function $randomToken(randomness) { return crypto.randomBytes(randomness).toString('hex'); } /** * TODO: [๐Ÿคถ] Maybe export through `@promptbook/utils` or `@promptbook/random` package * TODO: Maybe use nanoid instead https://github.com/ai/nanoid */ /** * This error indicates errors during the execution of the pipeline * * @public exported from `@promptbook/core` */ class PipelineExecutionError extends Error { constructor(message) { // Added id parameter super(message); this.name = 'PipelineExecutionError'; // TODO: [๐Ÿ™] DRY - Maybe $randomId this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`; Object.setPrototypeOf(this, PipelineExecutionError.prototype); } } /** * TODO: [๐Ÿง ][๐ŸŒ‚] Add id to all errors */ /** * Counts number of characters in the text * * @public exported from `@promptbook/utils` */ function countCharacters(text) { // Remove null characters text = text.replace(/\0/g, ''); // Replace emojis (and also ZWJ sequence) with hyphens text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1'); text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1'); text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-'); return text.length; } /** * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,... * TODO: [๐Ÿง ][โœŒ๏ธ] Make some Promptbook-native token system */ /** * Number of characters per standard line with 11pt Arial font size. * * @public exported from `@promptbook/utils` */ const CHARACTERS_PER_STANDARD_LINE = 63; /** * Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing. * * @public exported from `@promptbook/utils` */ const LINES_PER_STANDARD_PAGE = 44; /** * TODO: [๐Ÿง ] Should be this `constants.ts` or `config.ts`? * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name */ /** * Counts number of lines in the text * * Note: This does not check only for the presence of newlines, but also for the length of the standard line. * * @public exported from `@promptbook/utils` */ function countLines(text) { if (text === '') { return 0; } text = text.replace('\r\n', '\n'); text = text.replace('\r', '\n'); const lines = text.split('\n'); return lines.reduce((count, line) => count + Math.max(Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 1), 0); } /** * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,... * TODO: [๐Ÿง ][โœŒ๏ธ] Make some Promptbook-native token system */ /** * Counts number of pages in the text * * Note: This does not check only for the count of newlines, but also for the length of the standard line and length of the standard page. * * @public exported from `@promptbook/utils` */ function countPages(text) { return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE); } /** * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,... * TODO: [๐Ÿง ][โœŒ๏ธ] Make some Promptbook-native token system */ /** * Counts number of paragraphs in the text * * @public exported from `@promptbook/utils` */ function countParagraphs(text) { return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length; } /** * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,... * TODO: [๐Ÿง ][โœŒ๏ธ] Make some Promptbook-native token system */ /** * Split text into sentences * * @public exported from `@promptbook/utils` */ function splitIntoSentences(text) { return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== ''); } /** * Counts number of sentences in the text * * @public exported from `@promptbook/utils` */ function countSentences(text) { return splitIntoSentences(text).length; } /** * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,... * TODO: [๐Ÿง ][โœŒ๏ธ] Make some Promptbook-native token system */ const defaultDiacriticsRemovalMap = [ { base: 'A', letters: '\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F', }, { base: 'AA', letters: '\uA732' }, { base: 'AE', letters: '\u00C6\u01FC\u01E2' }, { base: 'AO', letters: '\uA734' }, { base: 'AU', letters: '\uA736' }, { base: 'AV', letters: '\uA738\uA73A' }, { base: 'AY', letters: '\uA73C' }, { base: 'B', letters: '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181', }, { base: 'C', letters: '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E', }, { base: 'D', letters: '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0', }, { base: 'DZ', letters: '\u01F1\u01C4' }, { base: 'Dz', letters: '\u01F2\u01C5' }, { base: 'E', letters: '\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E', }, { base: 'F', letters: '\u0046\u24BB\uFF26\u1E1E\u0191\uA77B' }, { base: 'G', letters: '\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E', }, { base: 'H', letters: '\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D', }, { base: 'I', letters: '\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197', }, { base: 'J', letters: '\u004A\u24BF\uFF2A\u0134\u0248' }, { base: 'K', letters: '\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2', }, { base: 'L', letters: '\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780', }, { base: 'LJ', letters: '\u01C7' }, { base: 'Lj', letters: '\u01C8' }, { base: 'M', letters: '\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C' }, { base: 'N', letters: '\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4', }, { base: 'NJ', letters: '\u01CA' }, { base: 'Nj', letters: '\u01CB' }, { base: 'O', letters: '\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C', }, { base: 'OI', letters: '\u01A2' }, { base: 'OO', letters: '\uA74E' }, { base: 'OU', letters: '\u0222' }, { base: 'OE', letters: '\u008C\u0152' }, { base: 'oe', letters: '\u009C\u0153' }, { base: 'P', letters: '\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754', }, { base: 'Q', letters: '\u0051\u24C6\uFF31\uA756\uA758\u024A' }, { base: 'R', letters: '\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782', }, { base: 'S', letters: '\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784', }, { base: 'T', letters: '\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786', }, { base: 'TZ', letters: '\uA728' }, { base: 'U', letters: '\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244', }, { base: 'V', letters: '\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245' }, { base: 'VY', letters: '\uA760' }, { base: 'W', letters: '\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72', }, { base: 'X', letters: '\u0058\u24CD\uFF38\u1E8A\u1E8C' }, { base: 'Y', letters: '\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE', }, { base: 'Z', letters: '\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762', }, { base: 'a', letters: '\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250', }, { base: 'aa', letters: '\uA733' }, { base: 'ae', letters: '\u00E6\u01FD\u01E3' }, { base: 'ao', letters: '\uA735' }, { base: 'au', letters: '\uA737' }, { base: 'av', letters: '\uA739\uA73B' }, { base: 'ay', letters: '\uA73D' }, { base: 'b', letters: '\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253', }, { base: 'c', letters: '\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184', }, { base: 'd', letters: '\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A', }, { base: 'dz', letters: '\u01F3\u01C6' }, { base: 'e', letters: '\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD', }, { base: 'f', letters: '\u0066\u24D5\uFF46\u1E1F\u0192\uA77C' }, { base: 'g', letters: '\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F', }, { base: 'h', letters: '\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265', }, { base: 'hv', letters: '\u0195' }, { base: 'i', letters: '\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131', }, { base: 'j', letters: '\u006A\u24D9\uFF4A\u0135\u01F0\u0249' }, { base: 'k', letters: '\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3', }, { base: 'l', letters: '\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747', }, { base: 'lj', letters: '\u01C9' }, { base: 'm', letters: '\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F' }, { base: 'n', letters: '\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5', }, { base: 'nj', letters: '\u01CC' }, { base: 'o', letters: '\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275', }, { base: 'oi', letters: '\u01A3' }, { base: 'ou', letters: '\u0223' }, { base: 'oo', letters: '\uA74F' }, { base: 'p', letters: '\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755', }, { base: 'q', letters: '\u0071\u24E0\uFF51\u024B\uA757\uA759' }, { base: 'r', letters: '\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783', }, { base: 's', letters: '\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B', }, { base: 't', letters: '\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787', }, { base: 'tz', letters: '\uA729' }, { base: 'u', letters: '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289', }, { base: 'v', letters: '\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C' }, { base: 'vy', letters: '\uA761' }, { base: 'w', letters: '\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73', }, { base: 'x', letters: '\u0078\u24E7\uFF58\u1E8B\u1E8D' }, { base: 'y', letters: '\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF', }, { base: 'z', letters: '\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763', }, ]; /** * Map of letters from diacritic variant to diacritless variant * Contains lowercase and uppercase separatelly * * > "รก" => "a" * > "ฤ›" => "e" * > "ฤ‚" => "A" * > ... * * @public exported from `@promptbook/utils` */ const DIACRITIC_VARIANTS_LETTERS = {}; // tslint:disable-next-line: prefer-for-of for (let i = 0; i < defaultDiacriticsRemovalMap.length; i++) { const letters = defaultDiacriticsRemovalMap[i].letters; // tslint:disable-next-line: prefer-for-of for (let j = 0; j < letters.length; j++) { DIACRITIC_VARIANTS_LETTERS[letters[j]] = defaultDiacriticsRemovalMap[i].base; } } // <- TODO: [๐Ÿ“] Put to maker function to save execution time if not needed /* @see https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /** * Removes diacritic marks (accents) from characters in a string. * * Note: [๐Ÿ”‚] This function is idempotent. * * @param input The string containing diacritics to be normalized. * @returns The string with diacritics removed or normalized. * @public exported from `@promptbook/utils` */ function removeDiacritics(input) { /*eslint no-control-regex: "off"*/ return input.replace(/[^\u0000-\u007E]/g, (a) => { return DIACRITIC_VARIANTS_LETTERS[a] || a; }); } /** * TODO: [ะ–] Variant for cyrillic (and in general non-latin) letters */ /** * Counts number of words in the text * * @public exported from `@promptbook/utils` */ function countWords(text) { text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a'); text = removeDiacritics(text); // Add spaces before uppercase letters preceded by lowercase letters (for camelCase) text = text.replace(/([a-z])([A-Z])/g, '$1 $2'); return text.split(/[^a-zะฐ-ั0-9]+/i).filter((word) => word.length > 0).length; } /** * TODO: [๐Ÿฅด] Implement counting in formats - like JSON, CSV, XML,... * TODO: [๐Ÿง ][โœŒ๏ธ] Make some Promptbook-native token system * TODO: [โœŒ๏ธ] `countWords` should be just `splitWords(...).length`, and all other counters should use this pattern as well */ /** * Helper of usage compute * * @param content the content of prompt or response * @returns part of UsageCounts * * @private internal utility of LlmExecutionTools */ function computeUsageCounts(content) { return { charactersCount: { value: countCharacters(content) }, wordsCount: { value: countWords(content) }, sentencesCount: { value: countSentences(content) }, linesCount: { value: countLines(content) }, paragraphsCount: { value: countParagraphs(content) }, pagesCount: { value: countPages(content) }, }; } /** * Freezes the given object and all its nested objects recursively * * Note: `$` is used to indicate that this function is not a pure function - it mutates given object * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object * * @returns The same object as the input, but deeply frozen * @public exported from `@promptbook/utils` */ function $deepFreeze(objectValue) { if (Array.isArray(objectValue)) { return Object.freeze(objectValue.map((item) => $deepFreeze(item))); } const propertyNames = Object.getOwnPropertyNames(objectValue); for (const propertyName of propertyNames) { const value = objectValue[propertyName]; if (value && typeof value === 'object') { $deepFreeze(value); } } Object.freeze(objectValue); return objectValue; } /** * TODO: [๐Ÿง ] Is there a way how to meaningfully test this utility */ /** * Represents the uncertain value * * @public exported from `@promptbook/core` */ const ZERO_VALUE = $deepFreeze({ value: 0 }); /** * Represents the uncertain value * * @public exported from `@promptbook/core` */ const UNCERTAIN_ZERO_VALUE = $deepFreeze({ value: 0, isUncertain: true }); /** * Represents the usage with no resources consumed * * @public exported from `@promptbook/core` */ $deepFreeze({ price: ZERO_VALUE, input: { tokensCount: ZERO_VALUE, charactersCount: ZERO_VALUE, wordsCount: ZERO_VALUE, sentencesCount: ZERO_VALUE, linesCount: ZERO_VALUE, paragraphsCount: ZERO_VALUE, pagesCount: ZERO_VALUE, }, output: { tokensCount: ZERO_VALUE, charactersCount: ZERO_VALUE, wordsCount: ZERO_VALUE, sentencesCount: ZERO_VALUE, linesCount: ZERO_VALUE, paragraphsCount: ZERO_VALUE, pagesCount: ZERO_VALUE, }, }); /** * Represents the usage with unknown resources consumed * * @public exported from `@promptbook/core` */ $deepFreeze({ price: UNCERTAIN_ZERO_VALUE, input: { tokensCount: UNCERTAIN_ZERO_VALUE, charactersCount: UNCERTAIN_ZERO_VALUE, wordsCount: UNCERTAIN_ZERO_VALUE, sentencesCount: UNCERTAIN_ZERO_VALUE, linesCount: UNCERTAIN_ZERO_VALUE, paragraphsCount: UNCERTAIN_ZERO_VALUE, pagesCount: UNCERTAIN_ZERO_VALUE, }, output: { tokensCount: UNCERTAIN_ZERO_VALUE, charactersCount: UNCERTAIN_ZERO_VALUE, wordsCount: UNCERTAIN_ZERO_VALUE, sentencesCount: UNCERTAIN_ZERO_VALUE, linesCount: UNCERTAIN_ZERO_VALUE, paragraphsCount: UNCERTAIN_ZERO_VALUE, pagesCount: UNCERTAIN_ZERO_VALUE, }, }); /** * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name */ /** * Make UncertainNumber * * @param value value of the uncertain number, if `NaN` or `undefined`, it will be set to 0 and `isUncertain=true` * @param isUncertain if `true`, the value is uncertain, otherwise depends on the value * * @private utility for initializating UncertainNumber */ function uncertainNumber(value, isUncertain) { if (value === null || value === undefined || Number.isNaN(value)) { return UNCERTAIN_ZERO_VALUE; } if (isUncertain === true) { return { value, isUncertain }; } return { value }; } /** * Simple wrapper `new Date().toISOString()` * * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time * * @returns string_date branded type * @public exported from `@promptbook/utils` */ function $getCurrentDate() { return new Date().toISOString(); } /** * @private util of `@promptbook/color` * @de */ class TakeChain { constructor(value) { this.value = value; } then(callback) { const newValue = callback(this.value); return take(newValue); } } /** * A function that takes an initial value and returns a proxy object with chainable methods. * * @param {*} initialValue - The initial value. * @returns {Proxy<WithTake<TValue>>} - A proxy object with a `take` method. * * @private util of `@promptbook/color` * @deprecated [๐Ÿคก] Use some better functional library instead of `TakeChain` */ function take(initialValue) { if (initialValue instanceof TakeChain) { return initialValue; } return new Proxy(new TakeChain(initialValue), { get(target, property, receiver) { if (Reflect.has(target, property)) { return Reflect.get(target, property, receiver); } else if (Reflect.has(initialValue, property)) { return Reflect.get(initialValue, property, receiver); } else { return undefined; } }, }); } /** * ๐ŸŽจ List of all 140 color names which are supported by CSS * * @public exported from `@promptbook/color` */ const CSS_COLORS = { transparent: 'rgba(0,0,0,0)', aliceblue: '#f0f8ff', antiquewhite: '#faebd7', aqua: '#00ffff', aquamarine: '#7fffd4', azure: '#f0ffff', beige: '#f5f5dc', bisque: '#ffe4c4', black: '#000000', blanchedalmond: '#ffebcd', blue: '#0000ff', blueviolet: '#8a2be2', brown: '#a52a2a', burlywood: '#deb887', cadetblue: '#5f9ea0', chartreuse: '#7fff00', chocolate: '#d2691e', coral: '#ff7f50', cornflowerblue: '#6495ed', cornsilk: '#fff8dc', crimson: '#dc143c', cyan: '#00ffff', darkblue: '#00008b', darkcyan: '#008b8b', darkgoldenrod: '#b8860b', darkgray: '#a9a9a9', darkgrey: '#a9a9a9', darkgreen: '#006400', darkkhaki: '#bdb76b', darkmagenta: '#8b008b', darkolivegreen: '#556b2f', darkorange: '#ff8c00', darkorchid: '#9932cc', darkred: '#8b0000', darksalmon: '#e9967a', darkseagreen: '#8fbc8f', darkslateblue: '#483d8b', darkslategray: '#2f4f4f', darkslategrey: '#2f4f4f', darkturquoise: '#00ced1', darkviolet: '#9400d3', deeppink: '#ff1493', deepskyblue: '#00bfff', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1e90ff', firebrick: '#b22222', floralwhite: '#fffaf0', forestgreen: '#228b22', fuchsia: '#ff00ff', gainsboro: '#dcdcdc', ghostwhite: '#f8f8ff', gold: '#ffd700', goldenrod: '#daa520', gray: '#808080', grey: '#808080', green: '#008000', greenyellow: '#adff2f', honeydew: '#f0fff0', hotpink: '#ff69b4', indianred: '#cd5c5c', indigo: '#4b0082', ivory: '#fffff0', khaki: '#f0e68c', lavender: '#e6e6fa', lavenderblush: '#fff0f5', lawngreen: '#7cfc00', lemonchiffon: '#fffacd', lightblue: '#add8e6', lightcoral: '#f08080', lightcyan: '#e0ffff', lightgoldenrodyellow: '#fafad2', lightgray: '#d3d3d3', lightgrey: '#d3d3d3', lightgreen: '#90ee90', lightpink: '#ffb6c1', lightsalmon: '#ffa07a', lightseagreen: '#20b2aa', lightskyblue: '#87cefa', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#b0c4de', lightyellow: '#ffffe0', lime: '#00ff00', limegreen: '#32cd32', linen: '#faf0e6', magenta: '#ff00ff', maroon: '#800000', mediumaquamarine: '#66cdaa', mediumblue: '#0000cd', mediumorchid: '#ba55d3', mediumpurple: '#9370db', mediumseagreen: '#3cb371', mediumslateblue: '#7b68ee', mediumspringgreen: '#00fa9a', mediumturquoise: '#48d1cc', mediumvioletred: '#c71585', midnightblue: '#191970', mintcream: '#f5fffa', mistyrose: '#ffe4e1', moccasin: '#ffe4b5', navajowhite: '#ffdead', navy: '#000080', oldlace: '#fdf5e6', olive: '#808000', olivedrab: '#6b8e23', orange: '#ffa500', orangered: '#ff4500', orchid: '#da70d6', palegoldenrod: '#eee8aa', palegreen: '#98fb98', paleturquoise: '#afeeee', palevioletred: '#db7093', papayawhip: '#ffefd5', peachpuff: '#ffdab9', peru: '#cd853f', pink: '#ffc0cb', plum: '#dda0dd', powderblue: '#b0e0e6', purple: '#800080', rebeccapurple: '#663399', red: '#ff0000', rosybrown: '#bc8f8f', royalblue: '#4169e1', saddlebrown: '#8b4513', salmon: '#fa8072', sandybrown: '#f4a460', seagreen: '#2e8b57', seashell: '#fff5ee', sienna: '#a0522d', silver: '#c0c0c0', skyblue: '#87ceeb', slateblue: '#6a5acd', slategray: '#708090', slategrey: '#708090', snow: '#fffafa', springgreen: '#00ff7f', steelblue: '#4682b4', tan: '#d2b48c', teal: '#008080', thistle: '#d8bfd8', tomato: '#ff6347', turquoise: '#40e0d0', violet: '#ee82ee', wheat: '#f5deb3', white: '#ffffff', whitesmoke: '#f5f5f5', yellow: '#ffff00', yellowgreen: '#9acd32', }; /** * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name */ /** * Validates that a channel value is a valid number within the range of 0 to 255. * Throws an error if the value is not valid. * * @param channelName - The name of the channel being validated. * @param value - The value of the channel to validate. * @throws Will throw an error if the value is not a valid channel number. * * @private util of `@promptbook/color` */ function checkChannelValue(channelName, value) { if (typeof value !== 'number') { throw new Error(`${channelName} channel value is not number but ${typeof value}`); } if (isNaN(value)) { throw new Error(`${channelName} channel value is NaN`); } if (Math.round(value) !== value) { throw new Error(`${channelName} channel is not whole number, it is ${value}`); } if (value < 0) { throw new Error(`${channelName} channel is lower than 0, it is ${value}`); } if (value > 255) { throw new Error(`${channelName} channel is greater than 255, it is ${value}`); } } /** * Color object represents an RGB color with alpha channel * * Note: There is no fromObject/toObject because the most logical way to serialize color is as a hex string (#009edd) * * @public exported from `@promptbook/color` */ class Color { /** * Creates a new Color instance from miscellaneous formats * - It can receive Color instance and just return the same instance * - It can receive color in string format for example `#009edd`, `rgb(0,158,221)`, `rgb(0%,62%,86.7%)`, `hsl(197.1,100%,43.3%)` * * Note: This is not including fromImage because detecting color from an image is heavy task which requires async stuff and we cannot safely determine with overloading if return value will be a promise * * @param color * @returns Color object */ static from(color) { if (color instanceof Color) { return take(color); } else if (Color.isColor(color)) { return take(color); } else if (typeof color === 'string') { return Color.fromString(color); } else { console.error({ color }); throw new Error(`Can not create color from given object`); } } /** * Creates a new Color instance from miscellaneous string formats * * @param color as a string for example `#009edd`, `rgb(0,158,221)`, `rgb(0%,62%,86.7%)`, `hsl(197.1,100%,43.3%)`, `red`, `darkgrey`,... * @returns Color object */ static fromString(color) { if (CSS_COLORS[color]) { return Color.fromString(CSS_COLORS[color]); // ----- } else if (Color.isHexColorString(color)) { return Color.fromHex(color); // ----- } else if (/^hsl\(\s*(\d+)\s*,\s*(\d+(?:\.\d+)?%)\s*,\s*(\d+(?:\.\d+)?%)\)$/.test(color)) { return Color.fromHsl(color); // ----- } else if (/^rgb\((\s*[0-9-.%]+\s*,?){3}\)$/.test(color)) { // TODO: [0] Should be fromRgbString and fromRgbaString one or two functions return Color.fromRgbString(color); // ----- } else if (/^rgba\((\s*[0-9-.%]+\s*,?){4}\)$/.test(color)) { return Color.fromRgbaString(color); // ----- } else { throw new Error(`Can not create a new Color instance from string "${color}".`); } } /** * Gets common color * * @param key as a css string like `midnightblue` * @returns Color object */ static get(key) { if (!CSS_COLORS[key]) { throw new Error(`"${key}" is not a common css color.`); } return Color.fromString(CSS_COLORS[key]); } /** * Creates a new Color instance from average color of given image * * @param image as a source for example `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYJh39z8ABJgCe/ZvAS4AAAAASUVORK5CYII=` * @returns Color object */ static async fromImage(image) { return Color.fromHex(`#009edd`); } /** * Creates a new Color instance from color in hex format * * @param color in hex for example `#009edd`, `009edd`, `#555`,... * @returns Color object */ static fromHex(hex) { const hexOriginal = hex; if (hex.startsWith('#')) { hex = hex.substring(1); } if (hex.length === 3) { return Color.fromHex3(hex); } if (hex.length === 4) { return Color.fromHex4(hex); } if (hex.length === 6) { return Color.fromHex6(hex); } if (hex.length === 8) { return Color.fromHex8(hex); } throw new Error(`Can not parse color from hex string "${hexOriginal}"`); } /** * Creates a new Color instance from color in hex format with 3 color digits (without alpha channel) * * @param color in hex for example `09d` * @returns Color object */ static fromHex3(hex) { const r = parseInt(hex.substr(0, 1), 16) * 16; const g = parseInt(hex.substr(1, 1), 16) * 16; const b = parseInt(hex.substr(2, 1), 16) * 16; return take(new Color(r, g, b)); } /** * Creates a new Color instance from color in hex format with 4 digits (with alpha channel) * * @param color in hex for example `09df` * @returns Color object */ static fromHex4(hex) { const r = parseInt(hex.substr(0, 1), 16) * 16; const g = parseInt(hex.substr(1, 1), 16) * 16; const b = parseInt(hex.substr(2, 1), 16) * 16; const a = parseInt(hex.substr(3, 1), 16) * 16; return take(new Color(r, g, b, a)); } /** * Creates a new Color instance from color in hex format with 6 color digits (without alpha channel) * * @param color in hex for example `009edd` * @returns Color object */ static fromHex6(hex) { const r = parseInt(hex.substr(0, 2), 16); const g = parseInt(hex.substr(2, 2), 16); const b = parseInt(hex.substr(4, 2), 16); return take(new Color(r, g, b)); } /** * Creates a new Color instance from color in hex format with 8 color digits (with alpha channel) * * @param color in hex for example `009edd` * @returns Color object */ static fromHex8(hex) { const r = parseInt(hex.substr(0, 2), 16); const g = parseInt(hex.substr(2, 2), 16); const b = parseInt(hex.substr(4, 2), 16); const a = parseInt(hex.substr(6, 2), 16); return take(new Color(r, g, b, a)); } /** * Creates a new Color instance from color in hsl format * * @param color as a hsl for example `hsl(197.1,100%,43.3%)` * @returns Color object */ static fromHsl(hsl) { const match = hsl.match(/^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/); if (!match) { throw new Error(`Invalid hsl string format: "${hsl}"`); } const h = parseFloat(match[1]); const s = parseFloat(match[2]) / 100; const l = parseFloat(match[3]) / 100; // HSL to RGB conversion const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); const m = l - c / 2; let r1 = 0, g1 = 0, b1 = 0; if (h >= 0 && h < 60) { r1 = c; g1 = x; b1 = 0; } else if (h >= 60 && h < 120) { r1 = x; g1 = c; b1 = 0; } else if (h >= 120 && h < 180) { r1 = 0; g1 = c; b1 = x; } else if (h >= 180 && h < 240) { r1 = 0; g1 = x; b1 = c; } else if (h >= 240 && h < 300) { r1 = x; g1 = 0; b1 = c; } else if (h >= 300 && h < 360) { r1 = c; g1 = 0; b1 = x; } const r = Math.round((r1 + m) * 255); const g = Math.round((g1 + m) * 255); const b = Math.round((b1 + m) * 255); return take(new Color(r, g, b)); } /** * Creates a new Color instance from color in rgb format * * @param color as a rgb for example `rgb(0,158,221)`, `rgb(0%,62%,86.7%)` * @returns Color object */ static fromRgbString(rgb) { const match = rgb.match(/^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/); if (!match) { throw new Error(`Invalid rgb string format: "${rgb}"`); } const parseChannel = (value) => { if (value.endsWith('%')) { // Percentage value const percent = parseFloat(value); return Math.round((percent / 100) * 255); } else { // Numeric value return Math.round(parseFloat(value)); } }; const r = parseChannel(match[1]); const g = parseChannel(match[2]); const b = parseChannel(match[3]); return take(new Color(r, g, b)); } /** * Creates a new Color instance from color in rbga format * * @param color as a rgba for example `rgba(0,158,221,0.5)`, `rgb(0%,62%,86.7%,50%)` * @returns Color object */ static fromRgbaString(rgba) { const match = rgba.match(/^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/); if (!match) { throw new Error(`Invalid rgba string format: "${rgba}"`); } const parseChannel = (value) => { if (value.endsWith('%')) { const percent = parseFloat(value); return Math.round((percent / 100) * 255); } else { return Math.round(parseFloat(value)); } }; const parseAlpha = (value) => { if (value.endsWith('%')) { const percent = parseFloat(value); return Math.round((percent / 100) * 255); } else { const alphaFloat = parseFloat(value); // If alpha is between 0 and 1, treat as float if (alphaFloat <= 1) { return Math.round(alphaFloat * 255); } // Otherwise, treat as 0-255 return Math.round(alphaFloat); } }; const r = parseChannel(match[1]); const g = parseChannel(match[2]); const b = parseChannel(match[3]); const a = parseAlpha(match[4]); return take(new Color(r, g, b, a)); } /** * Creates a new Color for color channels values * * @param red number from 0 to 255 * @param green number from 0 to 255 * @param blue number from 0 to 255 * @param alpha number from 0 (transparent) to 255 (opaque = default) * @returns Color object */ static fromValues(red, green, blue, alpha = 255) { return take(new Color(red, green, blue, alpha)); } /** * Checks if the given value is a valid Color object. * * @param {unknown} value - The value to check. * @return {value is WithTake<Color>} Returns true if the value is a valid Color object, false otherwise. */ static isColor(value) { if (typeof value !== 'object') { return false; } if (value === null) { return false; } if (typeof value.red !== 'number' || typeof value.green !== 'number' || typeof value.blue !== 'number' || typeof value.alpha !== 'number') { return false; } if (typeof value.then !== 'function') { return false; } return true; } /** * Checks if the given value is a valid hex color string * * @param value - value to check * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.) */ static isHexColorString(value) { return (typeof value === 'string' && /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value)); } /** * Creates new Color object * * Note: Consider using one of static methods like `from` or `fromString` * * @param red number from 0 to 255 * @param green number from 0 to 255 * @param blue number from 0 to 255 * @param alpha number from 0 (transparent) to 255 (opaque) */ constructor(red, green, blue, alpha = 255) { this.red = red; this.green = green; this.blue = blue; this.alpha = alpha; checkChannelValue('Red', red); checkChannelValue('Green', green); checkChannelValue('Blue', blue); checkChannelValue('Alpha', alpha); } /** * Shortcut for `red` property * Number from 0 to 255 * @alias red */ get r() { return this.red; } /** * Shortcut for `green` property * Number from 0 to 255 * @alias green */ get g() { return this.green; } /** * Shortcut for `blue` property * Number from 0 to 255 * @alias blue */ get b() { return this.blue; } /** * Shortcut for `alpha` property * Number from 0 (transparent) to 255 (opaque) * @alias alpha */ get a() { return this.alpha; } /** * Shortcut for `alpha` property * Number from 0 (transparent) to 255 (opaque) * @alias alpha */ get opacity() { return this.alpha; } /** * Shortcut for 1-`alpha` property */ get transparency() { return 255 - this.alpha; } clone() { return take(new Color(this.red, this.green, this.blue, this.alpha)); } toString() { return this.toHex(); } toHex() { if (this.alpha === 255) { return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue .toString(16) .padStart(2, '0')}`; } else { return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue .toString(16) .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`; } } toRgb() { if (this.alpha === 255) { return `rgb(${this.red}, ${this.green}, ${this.blue})`; } else { return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`; } } toHsl() { throw new Error(`Getting HSL is not implemented`); } } /** * TODO: [๐Ÿฅป] Split Color class and color type * TODO: For each method a corresponding static method should be created * Like clone can be done by color.clone() OR Color.clone(color) * TODO: Probably as an independent LIB OR add to LIB xyzt (ask @roseckyj) * TODO: !! Transfer back to Collboard (whole directory) * TODO: Maybe [๐ŸŒ๏ธโ€โ™‚๏ธ] change ACRY toString => (toHex) toRgb when there will be toRgb and toRgba united * TODO: Convert getters to methods - getters only for values * TODO: Write tests * TODO: Getters for alpha, opacity, transparency, r, b, g, h, s, l, a,... * TODO: [0] Should be fromRgbString and fromRgbaString one or two functions + one or two regex * TODO: Use rgb, rgba, hsl for testing and parsing with the same regex * TODO: Regex for rgb, rgba, hsl does not support all options like deg, rad, turn,... * TODO: Convolution matrix * TODO: Maybe connect with textures */ /** * Makes color transformer which returns a grayscale version of the color * * @param amount from 0 to 1 * * @public exported from `@promptbook/color` */ function grayscale(amount) { return ({ red, green, blue, alpha }) => { const average = (red + green + blue) / 3; red = Math.round(average * amount + red * (1 - amount)); green = Math.round(average * amount + green * (1 - amount)); blue = Math.round(average * amount + blue * (1 - amount)); return Color.fromValues(red, green, blue, alpha); }; } /** * Converts HSL values to RGB values * * @param hue [0-1] * @param saturation [0-1] * @param lightness [0-1] * @returns [red, green, blue] [0-255] * * @private util of `@promptbook/color` */ function hslToRgb(hue, saturation, lightness) { let red; let green; let blue; if (saturation === 0) { // achromatic red = lightness; green = lightness; blue = lightness; } else { // TODO: Extract to separate function const hue2rgb = (p, q, t) => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; const q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation; const p = 2 * lightness - q; red = hue2rgb(p, q, hue + 1 / 3); green = hue2rgb(p, q, hue); blue = hue2rgb(p, q, hue - 1 / 3); } return [Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255)]; } /** * TODO: Properly name all used internal variables */ /** * Converts RGB values to HSL values * * @param red [0-255] * @param green [0-255] * @param blue [0-255] * @returns [hue, saturation, lightness] [0-1] * * @private util of `@promptbook/color` */ function rgbToHsl(red, green, blue) { red /= 255; green /= 255; blue /= 255;