pomljs
Version:
Prompt Orchestration Markup Language
303 lines (300 loc) • 17.2 kB
JavaScript
import * as React from 'react';
import { component, irElement, trimChildrenWhiteSpace, ReadError } from './base.js';
/**
* NOTE: The components in this file are the lowest-level APIs and for internal use only.
*
* There are two main ways to present data: as markup or as serialized data.
* 1. Markup is to be rendered as markup languages like Markdown, Wikitext, etc.
* 2. Serialized data is to be rendered as JSON, XML, etc.
*
* When rendered as serialized data, the framework will output key-value pairs,
* objects, arrays, etc. instead of bolds, italics, headers that are considered
* helpful for human readers in markup texts.
*
* HTML is a special case. It can be considered as a markup language because it
* has the tags that are used to format the text. However, it can also be considered
* as serialized data when using its tags to represent key-value pairs.
*
* Presentation can be configured in stylesheet, but it's a special style that can
* be propagated down to the children components. All other styles are only set for
* current active component, including the serializer language, which only affects
* the enclosing environment of the markup/serialized.
*/
const DefaultMarkupLang = 'markdown';
const DefaultSerializer = 'json';
// The context that stores the current presentation appraoch.
// The language is preserved in the context,
// because we need to know whether to create a environment with a different lang.
const PresentationApproach = React.createContext(undefined);
const computePresentationOrUndefined = (props) => {
if (props.presentation === 'markup' ||
props.presentation === 'serialize' ||
props.presentation === 'free' ||
props.presentation === 'multimedia') {
return props.presentation;
}
else if (props.presentation) {
throw ReadError.fromProps(`Invalid presentation: ${props.presentation}`, props);
}
const presentation = React.useContext(PresentationApproach);
return presentation?.presentation;
};
var Markup;
(function (Markup) {
/**
* Encloses a markup component.
* It could produce nothing if it's not necessary to wrap the component.
*/
Markup.Environment = component('Markup.Environment')((props) => {
const parentPresentation = React.useContext(PresentationApproach);
// presentation is extracted but not used here. We are already in markup mode.
let { presentation, markupLang, children, originalStartIndex, originalEndIndex, writerOptions, sourcePath } = props;
if (!markupLang) {
if (parentPresentation?.presentation === 'markup') {
markupLang = parentPresentation.markupLang;
}
else {
markupLang = DefaultMarkupLang;
}
}
return parentPresentation?.presentation === 'markup' &&
parentPresentation.markupLang === markupLang &&
(!writerOptions ||
parentPresentation.writerOptions === writerOptions) ? (React.createElement(React.Fragment, null, children)) : (irElement('env', { presentation: 'markup', markupLang, writerOptions, originalStartIndex, originalEndIndex, sourcePath }, React.createElement(PresentationApproach.Provider, { value: {
presentation: 'markup',
markupLang: markupLang,
writerOptions: writerOptions
} }, trimChildrenWhiteSpace(children, props))));
});
Markup.EncloseSerialize = component('Markup.EncloseSerialize')((props) => {
const { children, inline = false, ...others } = props;
return (React.createElement(Markup.Code, { inline: inline, ...others }, children));
});
const SimpleMarkupComponent = (props) => {
// Sometimes it helps to extract attributes like markupLang to avoid too many props sent to IR.
// But this is not necessary.
const { children, tagName, markupLang, presentation, ...others } = props;
return (React.createElement(Markup.Environment, { markupLang: markupLang, presentation: presentation, ...others }, irElement(tagName, others, children)));
};
const HeaderLevel = React.createContext(1);
// Paragraph is a block preceded by a newline and followed by a newline.
// The paragraph in our context is the most common block element.
// It can be nested, and represent complex sections and rich texts.
Markup.Paragraph = component('Markup.Paragraph')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "p" }, children));
});
// Inline is a light-weight element that is wrapped by two spaces.
Markup.Inline = component('Markup.Inline')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "span" }, children));
});
Markup.Newline = component('Markup.Newline')((props) => {
const { newlineCount, ...others } = props;
return (React.createElement(Markup.Environment, { ...others }, irElement('nl', { count: newlineCount, ...others })));
});
// Header is a block that is usually used to emphasize the title of a section.
Markup.Header = component('Markup.Header')((props) => {
const ctxLevel = React.useContext(HeaderLevel);
const { children, ...others } = props;
return (React.createElement(Markup.Environment, { ...others }, irElement('h', {
level: ctxLevel,
...others
}, children)));
});
// SubContent usually follows a header and states that the headers inside it are sub-headers.
// It's same as a paragraph in all other aspects.
Markup.SubContent = component('Markup.SubContent')((props) => {
const { children, ...others } = props;
const ctxLevel = React.useContext(HeaderLevel);
// needn't trim here.
return (React.createElement(HeaderLevel.Provider, { value: ctxLevel + 1 },
React.createElement(Markup.Paragraph, { ...others }, children)));
});
// Bold is a Inline element that is used to emphasize the text.
Markup.Bold = component('Markup.Bold')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "b" }, children));
});
// Italic is a Inline element that is used to emphasize the text.
Markup.Italic = component('Markup.Italic')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "i" }, children));
});
// Strikethrough is a Inline element that is used to represent deleted text.
Markup.Strikethrough = component('Markup.Strikethrough')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "s" }, children));
});
// Underline is a Inline element that is used to represent underlined text.
Markup.Underline = component('Markup.Underline')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "u" }, children));
});
Markup.Code = component('Markup.Code')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "code" }, children));
});
Markup.ListContext = React.createContext(undefined);
Markup.ListItemIndexContext = React.createContext(0);
Markup.List = component('Markup.List')((props) => {
const { children, listStyle = 'dash', ...others } = props;
if (!['star', 'dash', 'plus', 'decimal', 'latin'].includes(listStyle)) {
throw ReadError.fromProps(`Invalid list style: ${listStyle}`, others);
}
return (React.createElement(Markup.Environment, { ...others }, irElement('list', { listStyle, ...others }, children)));
});
Markup.ListItem = component('Markup.ListItem')((props) => {
const { children, ...others } = props;
return irElement('item', { ...others }, children);
});
Markup.TableContainer = component('Markup.TableContainer')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "table" }, children));
});
Markup.TableHead = component('Markup.TableHead')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "thead" }, children));
});
Markup.TableBody = component('Markup.TableBody')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "tbody" }, children));
});
Markup.TableRow = component('Markup.TableRow')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "trow" }, children));
});
Markup.TableCell = component('Markup.TableCell')((props) => {
const { children, ...others } = props;
return (React.createElement(SimpleMarkupComponent, { ...others, tagName: "tcell" }, children));
});
})(Markup || (Markup = {}));
var Serialize;
(function (Serialize) {
/**
* Encloses a serialize component.
* It becomes transparent when already in a serialized environment.
*
* When environment exists, the writer does things to render the environment.
* How environment handles its child elements is very similar to the Any component,
* except when the environment only contains a single element, in which case it will be directly returned
* if it's unnamed.
*/
Serialize.Environment = component('Serialize.Environment')((props) => {
const parentPresentation = React.useContext(PresentationApproach);
// presentation is extracted but not used here. We are already in serialize mode.
// The env IR element only accepts a limited subset. Make sure others is not used here.
let { presentation, serializer, children, originalStartIndex, originalEndIndex, writerOptions, sourcePath, ...others } = props;
if (!serializer) {
if (parentPresentation?.presentation === 'serialize') {
serializer = parentPresentation.serializer;
}
else {
serializer = DefaultSerializer;
}
}
let elem = parentPresentation?.presentation === 'serialize' &&
parentPresentation?.serializer === serializer &&
(!writerOptions || parentPresentation?.writerOptions === writerOptions) ? (React.createElement(React.Fragment, null, children)) : (irElement('env', {
presentation: 'serialize',
serializer,
originalStartIndex,
originalEndIndex,
writerOptions,
sourcePath
}, React.createElement(PresentationApproach.Provider, { value: {
presentation: 'serialize',
serializer: serializer,
writerOptions: writerOptions
} }, trimChildrenWhiteSpace(children, props))));
if (parentPresentation?.presentation === 'markup') {
// If the parent is in markup mode, we need a wrapper (e.g., ```json...```).
// TODO: support inline = true
elem = (React.createElement(Markup.EncloseSerialize, { inline: false, lang: serializer, ...others }, elem));
}
return elem;
});
/**
* Value is a single value or (usually) a pair of key and value. The behavior is as follows:
* 1. If the inner children are one single text element, it renders as a single value with the specified type.
* 2. Otherwise, it can be either a list or an object depending on its children.
*
* Detailed implementation might differ between different writers, but generally:
* 1. When the children contain all named values and type is not array, it presents as an object.
* 2. When the children contain unnamed values, it presents as a list.
* 3. When the children contain multiple elements including text elements, they are concatenated into a list.
*/
Serialize.Any = component('Serialize.Any')((props) => {
const { name, type, children, ...others } = props;
return (React.createElement(Serialize.Environment, { ...others }, irElement('any', { name, type, ...others }, children)));
});
// Object is to quickly insert an external data into the current document.
Serialize.Object = component('Serialize.Object')((props) => {
const { data, ...others } = props;
return (React.createElement(Serialize.Environment, { ...others }, irElement('obj', { data: JSON.stringify(data), ...others })));
});
})(Serialize || (Serialize = {}));
var Free;
(function (Free) {
/**
* The free environment marks the content as free-form text,
* which will be kept as is without any processing.
*/
Free.Environment = component('Free.Environment')((props) => {
const parentPresentation = React.useContext(PresentationApproach);
// presentation is extracted but not used here. We are already in serialize mode.
// The env IR element only accepts a limited subset. Make sure others is not used here.
const { presentation, children, originalStartIndex, originalEndIndex, writerOptions, sourcePath, whiteSpace = 'pre', ...others } = props;
let elem = parentPresentation?.presentation === 'free' &&
(!writerOptions || parentPresentation?.writerOptions === writerOptions) ? (React.createElement(React.Fragment, null, children)) : (irElement('env', { presentation: 'free', originalStartIndex, originalEndIndex, writerOptions, whiteSpace, sourcePath }, React.createElement(PresentationApproach.Provider, { value: {
presentation: 'free',
writerOptions: writerOptions
} }, trimChildrenWhiteSpace(children, { ...props, whiteSpace }))));
if (parentPresentation?.presentation === 'markup') {
// If the parent is in markup mode, we need a wrapper (e.g., ```...```).
// TODO: support inline = true
elem = (React.createElement(Markup.EncloseSerialize, { inline: false, ...others }, elem));
}
else if (parentPresentation?.presentation === 'serialize') {
// Make it a string
elem = React.createElement(Serialize.Any, { ...others }, elem);
}
return elem;
});
// This exists only because sometimes we needs to set attributes on free text.
// For example, class names and speakers.
Free.Text = component('Free.Text')((props) => {
const { children, whiteSpace = 'pre', ...others } = props;
return (React.createElement(Free.Environment, { whiteSpace: whiteSpace, ...others }, irElement('text', { whiteSpace, ...others }, children)));
});
})(Free || (Free = {}));
var MultiMedia;
(function (MultiMedia) {
MultiMedia.Environment = component('MultiMedia.Environment')((props) => {
const parentPresentation = React.useContext(PresentationApproach);
const { presentation, children, originalStartIndex, originalEndIndex, writerOptions, sourcePath, ...others } = props;
return parentPresentation?.presentation === 'multimedia' &&
(!writerOptions || parentPresentation?.writerOptions === writerOptions) ? (React.createElement(React.Fragment, null, children)) : (irElement('env', { presentation: 'multimedia', originalStartIndex, originalEndIndex, writerOptions, sourcePath }, React.createElement(PresentationApproach.Provider, { value: {
presentation: 'multimedia',
writerOptions: writerOptions
} }, trimChildrenWhiteSpace(children, props))));
});
MultiMedia.Image = component('MultiMedia.Image')((props) => {
const { ...others } = props;
return (React.createElement(MultiMedia.Environment, { ...others }, irElement('img', { ...others })));
});
MultiMedia.Audio = component('MultiMedia.Audio')((props) => {
const { ...others } = props;
return (React.createElement(MultiMedia.Environment, { ...others }, irElement('audio', { ...others })));
});
MultiMedia.ToolRequest = component('MultiMedia.ToolRequest')((props) => {
const { id, name, parameters, ...others } = props;
return (React.createElement(MultiMedia.Environment, { ...others }, irElement('toolrequest', { id, name, content: parameters, ...others })));
});
MultiMedia.ToolResponse = component('MultiMedia.ToolResponse')((props) => {
const { id, name, children, ...others } = props;
return (React.createElement(MultiMedia.Environment, { ...others }, irElement('toolresponse', { id, name, ...others }, children)));
});
})(MultiMedia || (MultiMedia = {}));
export { DefaultMarkupLang, DefaultSerializer, Free, Markup, MultiMedia, Serialize, computePresentationOrUndefined };
//# sourceMappingURL=presentation.js.map