pomljs
Version:
Prompt Orchestration Markup Language
573 lines (567 loc) • 24.1 kB
JavaScript
'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