UNPKG

pomljs

Version:

Prompt Orchestration Markup Language

303 lines (300 loc) 17.2 kB
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