UNPKG

pomljs

Version:

Prompt Orchestration Markup Language

573 lines (567 loc) 24.1 kB
'use strict'; var React = require('react'); var base = require('./base.cjs'); var presentation = require('./presentation.cjs'); var fs = require('fs'); var image = require('./util/image.cjs'); var audio = require('./util/audio.cjs'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React); const FREE_SYNTAXES = ['text']; const MARKUP_SYNTAXES = ['markdown', 'html', 'csv', 'tsv']; const SERIALIZE_SYNTAXES = ['json', 'yaml', 'xml']; const MULTIMEDIA_SYNTAXES = ['multimedia']; const computeSyntaxContext = (props, defaultSyntax, invalidPresentations) => { const { syntax, ...others } = props; invalidPresentations = invalidPresentations ?? ['multimedia']; // 1. Create the full presentation style based on the syntax shortcut. // This is the case when syntax is explicity specified. let presentationStyle; if (!syntax) { presentationStyle = {}; } else if (MARKUP_SYNTAXES.includes(syntax)) { if (invalidPresentations.includes('markup')) { throw base.ReadError.fromProps(`Markup syntax (${syntax}) is not supported here.`, others); } presentationStyle = { presentation: 'markup', markupLang: syntax }; } else if (SERIALIZE_SYNTAXES.includes(syntax)) { if (invalidPresentations.includes('serialize')) { throw base.ReadError.fromProps(`Serialize syntax (${syntax}) is not supported here.`, others); } presentationStyle = { presentation: 'serialize', serializer: syntax }; } else if (FREE_SYNTAXES.includes(syntax)) { if (invalidPresentations.includes('free')) { throw base.ReadError.fromProps(`Free syntax (${syntax}) is not supported here.`, others); } presentationStyle = { presentation: 'free' }; } else if (MULTIMEDIA_SYNTAXES.includes(syntax)) { if (invalidPresentations.includes('multimedia')) { throw base.ReadError.fromProps(`Multimedia syntax (${syntax}) is not supported here.`, others); } presentationStyle = { presentation: 'multimedia' }; } else { throw base.ReadError.fromProps(`Unsupported syntax: ${syntax}`, others); } // 2. Compute the presentation context. // Try to inherit presentation and syntax from parents. // There are two cases where the inherited presentation does not count. // (a) No presentation is found. // (b) The presentation is free and the syntax is not specified. const presentation$1 = presentation.computePresentationOrUndefined(presentationStyle); if (!presentation$1 || (presentation$1 === 'free' && !syntax)) { if (syntax) { // This should not happen. Must be a bug. throw base.ReadError.fromProps(`Syntax is specified (${syntax}) but presentation method is not found. Something is wrong.`, others); } // Try again with a default syntax return computeSyntaxContext({ ...others, syntax: defaultSyntax || 'markdown' }, defaultSyntax, invalidPresentations); } return presentation$1; }; // Helper component for contents that are designed for markup, but also work in other syntaxes. const AnyOrFree = base.component('AnyOrFree')((props) => { const { syntax, children, presentation: presentation$1, name, type, asAny, ...others } = props; if (presentation$1 === 'serialize') { if (asAny) { return (React__namespace.createElement(presentation.Serialize.Any, { serializer: syntax, name: name, type: type, ...others }, children)); } else { return (React__namespace.createElement(presentation.Serialize.Environment, { serializer: syntax, ...others }, children)); } } else if (presentation$1 === 'free') { return React__namespace.createElement(presentation.Free.Text, { ...others }, children); } else { throw base.ReadError.fromProps(`This component is not designed for ${presentation$1} syntaxes.`, others); } }); /** * Text (`<text>`, `<poml>`) is a wrapper for any contents. * By default, it uses `markdown` syntax and writes the contents within it directly to the output. * When used with "markup" syntaxes, it renders a standalone section preceded and followed by one blank line. * It's mostly used in the root element of a prompt, but it should also work in any other places. * This component will be automatically added as a wrapping root element if it's not provided: * 1. If the first element is pure text contents, `<poml syntax="text">` will be added. * 2. If the first element is a POML component, `<poml syntax="markdown">` will be added. * * @param {'markdown'|'html'|'json'|'yaml|'xml'|'text'} syntax - The syntax of the content. * @param className - A class name for quickly styling the current block with stylesheets. * @param {'human'|'ai'|'system'} speaker - The speaker of the content. By default, it's determined by the context and the content. * @param name - The name of the content, used in serialization. * @param type - The type of the content, used in serialization. * @param {object} writerOptions - An experimental optional JSON string to customize the format of markdown headers, JSON indents, etc. * * @example * ```xml * <poml syntax="text"> * Contents of the whole prompt. * * 1. Your customized list. * 2. You don't need to know anything about POML. * </poml> * ``` * * To render the whole prompt in markdown syntax with a "human" speaker: * * ```xml * <poml syntax="markdown" speaker="human"> * <p>You are a helpful assistant.</p> * <p>What is the capital of France?</p> * </poml> * ``` */ const Text = base.component('Text', ['div', 'poml'])((props) => { const { syntax, children, name, type, ...others } = props; const presentation = computeSyntaxContext(props, 'markdown'); if (presentation === 'markup') { return (React__namespace.createElement(Paragraph, { syntax: syntax, blankLine: false, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation, asAny: true, name: name, type: type, ...others }, children)); } }); /** * Paragraph (`<p>`) is a standalone section preceded by and followed by two blank lines in markup syntaxes. * It's mostly used for text contents. * * @param {boolean} blankLine - Whether to add one more blank line (2 in total) before and after the paragraph. * * @see {@link Text} for other props available. * * @example * ```xml * <p>Contents of the paragraph.</p> * ``` */ const Paragraph = base.component('Paragraph', ['p'])((props) => { const { syntax, children, name, type, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.Paragraph, { markupLang: syntax, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: true, name: name, type: type, ...others }, children)); } }); /** * Inline (`<span>`) is a container for inline content. * When used with markup syntaxes, it wraps text in an inline style, without any preceding or following blank characters. * In serializer syntaxes, it's treated as a generic value. * Inline elements are not designed to be used alone (especially in serializer syntaxes). * One might notice problematic renderings (e.g., speaker not applied) when using it alone. * * @param {'markdown'|'html'|'json'|'yaml'|'xml'|'text'} syntax - The syntax of the content. * @param className - A class name for quickly styling the current block with stylesheets. * @param {'human'|'ai'|'system'} speaker - The speaker of the content. By default, it's determined by the context and the content. * @param {object} writerOptions - An experimental optional JSON string to customize the format of markdown headers, JSON indents, etc. * * @example * ```xml * <p>I'm listening to <span>music</span> right now.</p> * ``` */ const Inline = base.component('Inline', ['span'])((props) => { const { syntax, children, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.Inline, { markupLang: syntax, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: false, ...others }, children)); } }); /** * Newline (`<br>`) explicitly adds a line break, primarily in markup syntaxes. * In serializer syntaxes, it's ignored. * * @param {number} newLineCount - The number of linebreaks to add. * * @see {@link Inline} for other props available. * * @example * ```xml * <br /> * ``` */ const Newline = base.component('Newline', ['br'])((props) => { const { syntax, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return React__namespace.createElement(presentation.Markup.Newline, { markupLang: syntax, ...others }); } else { return null; } }); /** * Header (`<h>`) renders headings in markup syntaxes. * It's commonly used to highlight titles or section headings. * The header level will be automatically computed based on the context. * Use SubContent (`<section>`) for nested content. * * @see {@link Paragraph} for other props available. * * @example * ```xml * <Header syntax="markdown">Section Title</Header> * ``` */ const Header = base.component('Header', ['h'])((props) => { const { syntax, children, name, type, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.Header, { markupLang: syntax, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: true, name: name, type: type, ...others }, children)); } }); /** * SubContent (`<section>`) renders nested content, often following a header. * The headers within the section will be automatically adjusted to a lower level. * * @see {@link Paragraph} for other props available. * * @example * ```xml * <h>Section Title</h> * <section> * <h>Sub-section Title</h> <!-- Nested header --> * <p>Sub-section details</p> * </section> * ``` */ const SubContent = base.component('SubContent', ['section'])((props) => { const { syntax, children, name, type, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.SubContent, { markupLang: syntax, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: true, name: name, type: type, ...others }, children)); } }); /** * Bold (`<b>`) emphasizes text in a bold style when using markup syntaxes. * * @see {@link Inline} for other props available. * * @example * ```xml * <p><b>Task:</b> Do something.</p> * ``` */ const Bold = base.component('Bold', ['b'])((props) => { const { syntax, children, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.Bold, { markupLang: syntax, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: false, ...others }, children)); } }); /** * Italic (`<i>`) emphasizes text in an italic style when using markup syntaxes. * * @see {@link Inline} for other props available. * * @example * ```xml * Your <i>italicized</i> text. * ``` */ const Italic = base.component('Italic', ['i'])((props) => { const { syntax, children, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.Italic, { markupLang: syntax, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: false, ...others }, children)); } }); /** * Strikethrough (`<s>`, `<strike>`) indicates removed or invalid text in markup syntaxes. * * @see {@link Inline} for other props available. * * @example * ```xml * <s>This messages is removed.</s> * ``` */ base.component('Strikethrough', ['s', 'strike'])((props) => { const { syntax, children, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.Strikethrough, { markupLang: syntax, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: false, ...others }, children)); } }); /** * Underline (`<u>`) draws a line beneath text in markup syntaxes. * * @see {@link Inline} for other props available. * * @example * ```xml * This text is <u>underlined</u>. * ``` */ base.component('Underline', ['u'])((props) => { const { syntax, children, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.Underline, { markupLang: syntax, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: false, ...others }, children)); } }); /** * Code is used to represent code snippets or inline code in markup syntaxes. * * @param {boolean} inline - Whether to render code inline or as a block. Default is `true`. * @param lang - The language of the code snippet. * * @see {@link Paragraph} for other props available. * * @example * ```xml * <code inline="true">const x = 42;</code> * ``` * * ```xml * <code lang="javascript"> * const x = 42; * </code> * ``` */ const Code = base.component('Code')((props) => { const { syntax, children, name, type, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.Code, { markupLang: syntax, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: true, name: name, type: type, ...others }, children)); } }); /** * List (`<list>`) is a container for multiple ListItem (`<item>`) elements. * When used with markup syntaxes, a bullet or numbering is added. * * @param {'star'|'dash'|'plus'|'decimal'|'latin'} listStyle - The style for the list marker, such as dash or star. Default is `dash`. * * @see {@link Paragraph} for other props available. * * @example * ```xml * <list listStyle="decimal"> * <item>Item 1</item> * <item>Item 2</item> * </list> * ``` */ const List = base.component('List')((props) => { const { syntax, children, listStyle, name, type, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.List, { markupLang: syntax, listStyle: listStyle, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: true, name: name, type: type ?? 'array', ...others }, children)); } }); /** * ListItem (`<item>`) is an item within a List component. * In markup mode, it is rendered with the specified bullet or numbering style. * * @see {@link Paragraph} for other props available. * * @example * ```xml * <list listStyle="decimal"> * <item blankLine="true">Item 1</item> * <item>Item 2</item> * </list> * ``` */ const ListItem = base.component('ListItem', ['item'])((props) => { const { syntax, children, name, type, ...others } = props; const presentation$1 = computeSyntaxContext(props); if (presentation$1 === 'markup') { return (React__namespace.createElement(presentation.Markup.ListItem, { markupLang: syntax, ...others }, children)); } else { return (React__namespace.createElement(AnyOrFree, { syntax: syntax, presentation: presentation$1, asAny: true, name: name, type: type, ...others }, children)); } }); /** * Object (`<obj>`, `<dataObj>`) displays external data or object content. * When in serialize mode, it's serialized according to the given serializer. * * @param {'markdown'|'html'|'json'|'yaml'|'xml'} syntax - The syntax or serializer of the content. Default is `json`. * @param {object} data - The data object to render. * * @see {@link Inline} for other props available. * * @example * ```xml * <Object syntax="json" data="{ key: 'value' }" /> * ``` */ const Object$1 = base.component('Object', ['obj', 'dataObj'])((props) => { const { syntax, children, ...others } = props; const presentation$1 = computeSyntaxContext(props, 'json'); if (presentation$1 === 'serialize') { return (React__namespace.createElement(presentation.Serialize.Object, { serializer: syntax, ...others }, children)); } else { return React__namespace.createElement(Text, { syntax: syntax }, JSON.stringify(props.data)); } }); /** * Image (`<img>`) displays an image in the content. * Alternatively, it can also be shown as an alt text by specifying the `syntax` prop. * Note that syntax must be specified as `multimedia` to show the image. * * @see {@link Inline} for other props available. * * @param {string} src - The path to the image file. * @param {string} alt - The alternative text to show when the image cannot be displayed. * @param {string} base64 - The base64 encoded image data. It can not be specified together with `src`. * @param {string} type - The MIME type of the image **to be shown**. If not specified, it will be inferred from the file extension. * If specified, the image will be converted to the specified type. Can be `image/jpeg`, `image/png`, etc., or without the `image/` prefix. * @param {'top'|'bottom'|'here'} position - The position of the image. Default is `here`. * @param {number} maxWidth - The maximum width of the image to be shown. * @param {number} maxHeight - The maximum height of the image to be shown. * @param {number} resize - The ratio to resize the image to to be shown. * @param {'markdown'|'html'|'json'|'yaml'|'xml'|'multimedia'} syntax - Only when specified as `multimedia`, the image will be shown. * Otherwise, the alt text will be shown. By default, it's `multimedia` when `alt` is not specified. Otherwise, it's undefined (inherit from parent). * * @example * ```xml * <Image src="path/to/image.jpg" alt="Image description" position="bottom" /> * ``` */ const Image = base.component('Image', { aliases: ['img'], asynchorous: true })((props) => { let { syntax, src, base64, alt, type, position, maxWidth, maxHeight, resize, ...others } = props; if (!alt) { syntax = syntax ?? 'multimedia'; } const presentation$1 = computeSyntaxContext({ ...props, syntax }, 'multimedia', []); if (presentation$1 === 'multimedia') { if (src) { if (base64) { throw base.ReadError.fromProps('Cannot specify both `src` and `base64`.', others); } src = base.expandRelative(src); if (!fs.existsSync(src)) { throw base.ReadError.fromProps(`Image file not found: ${src}`, others); } } else if (!base64) { throw base.ReadError.fromProps('Either `src` or `base64` must be specified.', others); } const image$1 = base.useWithCatch(image.preprocessImage({ src, base64, type, maxWidth, maxHeight, resize }), others); if (!image$1) { return null; } return (React__namespace.createElement(presentation.MultiMedia.Image, { presentation: presentation$1, base64: image$1.base64, position: position, type: image$1.mimeType, alt: alt, ...others })); } else { return React__namespace.createElement(Inline, { syntax: syntax }, alt); } }); /** * Audio (`<audio>`) embeds an audio file in the content. * * Accepts either a file path (`src`) or base64-encoded audio data (`base64`). * The MIME type can be provided via `type` or will be inferred from the file extension. * * @param {string} src - Path to the audio file. If provided, the file will be read and encoded as base64. * @param {string} base64 - Base64-encoded audio data. Cannot be used together with `src`. * @param {string} alt - The alternative text to show when the image cannot be displayed. * @param {string} type - The MIME type of the audio (e.g., audio/mpeg, audio/wav). If not specified, it will be inferred from the file extension. * The type must be consistent with the real type of the file. The consistency will NOT be checked or converted. * The type can be specified with or without the `audio/` prefix. * @param {'top'|'bottom'|'here'} position - The position of the image. Default is `here`. * @param {'markdown'|'html'|'json'|'yaml'|'xml'|'multimedia'} syntax - Only when specified as `multimedia`, the image will be shown. * Otherwise, the alt text will be shown. By default, it's `multimedia` when `alt` is not specified. Otherwise, it's undefined (inherit from parent). * * @example * ```xml * <Audio src="path/to/audio.mp3" /> * ``` * @example * ```xml * <Audio base64="..." type="audio/wav" /> * ``` */ base.component('Audio', { aliases: ['audio'], asynchorous: true })((props) => { let { syntax, src, base64, type, ...others } = props; const presentation$1 = computeSyntaxContext(props, 'multimedia', []); if (presentation$1 === 'multimedia') { if (src) { if (base64) { throw base.ReadError.fromProps('Cannot specify both `src` and `base64`.', others); } src = base.expandRelative(src); if (!fs.existsSync(src)) { throw base.ReadError.fromProps(`Audio file not found: ${src}`, others); } } else if (!base64) { throw base.ReadError.fromProps('Either `src` or `base64` must be specified.', others); } const audio$1 = base.useWithCatch(audio.preprocessAudio({ src, base64, type }), others); if (!audio$1) { return null; } return (React__namespace.createElement(presentation.MultiMedia.Audio, { presentation: presentation$1, base64: audio$1.base64, type: audio$1.mimeType, ...others })); } else { return null; } }); exports.AnyOrFree = AnyOrFree; exports.Bold = Bold; exports.Code = Code; exports.Header = Header; exports.Image = Image; exports.Inline = Inline; exports.Italic = Italic; exports.List = List; exports.ListItem = ListItem; exports.Newline = Newline; exports.Object = Object$1; exports.Paragraph = Paragraph; exports.SubContent = SubContent; exports.Text = Text; exports.computeSyntaxContext = computeSyntaxContext; //# sourceMappingURL=essentials.cjs.map