@indra.ai/deva.feecting
Version:
The Feecting Deva manages all the parsing of feecting language in deva.world.
293 lines (239 loc) • 12.2 kB
JavaScript
"use strict"
import fs from 'node:fs';
import path from 'node:path';
// set the __dirname
import {fileURLToPath} from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const basePath = path.join(__dirname, '..', '..');
function formatDate(d, format='long', time=false, locale='en-US') {
if (!d) d = Date.now();
d = new Date(d);
const formats = {
long: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' },
long_month: { year: 'numeric', month: 'long', day: 'numeric'},
short: { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' },
short_month: { year: 'numeric', month: 'short', day: 'numeric' },
year: { year: 'numeric' },
month: { month: 'long' },
day: { day: 'long' }
};
const theDate = d.toLocaleDateString(locale, formats[format]);
const theTime = d.toLocaleTimeString(locale);
if (format === 'time') return theTime;
return !time ? theDate : `${theDate} - ${theTime}`;
}
class Parser {
constructor(opts) {
this.id = opts.id;
this.key = opts.key;
this.cmd = opts.cmd;
this.params = opts.params;
this.vars = opts.vars || {};
this.talk = opts.talk || [];
this.text = opts.text || false;
this.html = false;
this.client = opts.client || false;
this.agent = opts.agent || false;
this.container = opts.container || false;
}
uid() {
return Math.floor(Math.random() * Date.now());
}
/***********
func: formatHTML
params: text - text string to format
describe: the main formatting function where feecting text is converted into HTML.
***********/
formatHTML() {
this.text = '\n' + this.text.replace(/\@\@/g, '@');
this.html = this.text
// main label formatting
.replace(/\n\s*::BEGIN:(\w+)?:?(.+)?/g, '<div class="CONTAINER $1" data-id="$2">')
.replace(/\n\s*::END:(\w+)?:?(md5|sha256|sha512)?:?(.+)?/g, '</div>')
.replace(/\n?\s+?\/\/(.+)/g, '<div class="comment">$1</div>')
.replace(/(\n\s*)(var):(.+)\r?/g, `$1<div class="item $2"><span class="label">$2</span><span class="value">$3</span></div>`)
.replace(/(\n\s*)(var)\[(.+)\]:(.+)\r?/g, `$1<div class="item $2"><span class="label">$3</span><span class="value">$4</span></div>`)
.replace(/(\n\s*)::begin:(\w+)?:?(.+)?/g, '$1<div class="box $2" data-id="$3">')
.replace(/(\n\s*)::end:(\w+)?:?(md5|sha256|sha512)?-?(.+)?/g, '$1</div>')
.replace(/(\n\s*)####\s(.+)/g, `$1<h4>$2</h4>`)
.replace(/(\n\s*)###\s(.+)/g, `$1<h3>$2</h3>`)
.replace(/(\n\s*)##\s(.+)/g, `$1<h2>$2</h2>`)
.replace(/(\n\s*)#\s(.+)/g, `$1<h1>$2</h1>`)
.replace(/(\n\s*)={4,}(\n)/g, `$1<hr class="double xsmall" />$2`)
.replace(/(\n\s*)={3}(\n)/g, `$1<hr class="double small" />$2`)
.replace(/(\n\s*)={2}(\n)/g, `$1<hr class="double medium" />$2`)
.replace(/(\n\s*)={1}(\n)/g, `$1<hr class="double large" />$2`)
.replace(/(\n\s*)-{4,}(\n)/g, `$1<hr class="single xsmall"/>$2`)
.replace(/(\n\s*)-{3}(\n)/g, `$1<hr class="single small"/>$2`)
.replace(/(\n\s*)-{2}(\n)/g, `$1<hr class="single medium"/>$2`)
.replace(/(\n\s*)-{1}(\n)/g, `$1<hr class="single large"/>$2`)
.replace(/(\*\*|__)(?=(?:(?:[^`]*`[^`\r\n]*`)*[^`]*$))(?![^\/<]*>.*<\/.+>)(.*?)\1/gi, `<strong>$2</strong>`)
.replace(/```(.*?)```/sg, '\n<pre><code>$1</code></pre>\n')
.replace(/`(.*?)`/gi, '<code>$1</code>')
.replace(/(\*\*)(.+)?(\*\*)/g, `<b>$2</b>`)
// .replace(/(\s)(\#\w+)(\b)/g, `$1<span class="tag hash">$2</span>$3`)
// .replace(/(\b)(\@.+?)(\b)/g, `$1<span class="tag person">$2</span>$3`)
// .replace(/(\b)(\$.+?)(\b)/g, `$1<span class="tag place">$2</span>$3`)
// .replace(/(\b)(\!.+?)(\b)/g, `$1<span class="tag bang">$2</span>$3`)
//
// .replace(/(\s)\_([#|@|$|!].+?)(\b)/gi, `$1$2$3`)
// image processing regex
.replace(/(\n\s*)img:\s?(.+)/g, `$1<div class="image"><img src="$2" /></div>`)
.replace(/(\n\s*)thumb:\s?(.+)/g, `$1<div class="thumbnail"><img src="$2" /></div>`)
.replace(/(\n\s*)(button)\[(.+)\]:\s?(.+)/g, '$1<button class="btn $2" title="$3" data-button="$4">$3</button>')
// // cmd/tty tag parser
// .replace(/(\n\s*)(cmd|tty):\s?(.+)\r?/g, `$1<div class="item $2"><span class="label">$2</span><span class="value"><button class="btn $2" alt="$2" data-$2="$3"></button>$3</span></div>`)
//
// .replace(/(\s\n*)(cmd|tty)\[(.+)\]:(.+)\r?/g, `$1<div class="item $2"><span class="label">$2</span><span class="value"><button class="btn $2" alt="$2 $4" data-$2="$4">$3</button></span></div>`)
// .replace(/(\s\n*)(law):(.+)\r?/g, `$1<div class="item $2"><button class="label" data-$2="#legal add $3">$2</button><span class="value">$3</span></div>`)
// .replace(/(\s\n*)(law)\[(.+)\]:(.+)\r?/g, `$1<div class="item $2"><button class="label" data-$2="#legal add:$3 $4">$2</button><span class="value">$4</span></div>`)
.replace(/(\n\s*)(\d+)\. (.+)/g, `$1<div class="number-item" data-label="$2">$3</div>`)
.replace(/(\n\s*)> (.+)\r?/g, `$1<div class="list-item">$2</div>`)
.replace(/(\n\s*)- (.+)\r?/g, `$1<div class="line-item">$2</div>`)
// pdf links
.replace(/(\n\s*)(pdf):\s?(.+)/g, `$1<div class="$2"><iframe width="100%" height="100%" src="$3" frameborder="0"></iframe></div>`)
// youtube links
.replace(/(\n\s*)(youtube):\s?(.+)/g, `$1<div class="center youtube-video-player"><iframe src="https://www.youtube.com/embed/$3" frameborder="0"></iframe></div>`)
// links
// link with just url
.replace(/(\n\s*)(link):\s?(.+)/g, `$1<div class="item $2"><span class="label">link</span><span class="value"><a href="$3" class="$2" alt="$2" target="_blank">$3</a></span></div>`)
// link with bracket label/text
.replace(/(\n\s*)(link)\[(.+)\]:\s?(.+)/g, `$1<div class="item $2"><span class="label">link</span><span class="value"><a href="$4" class="$2" alt="$3" target="$2">$3</a></span></div>`)
// numbered list item of href links
.replace(/(\n\s*)(href)\[(\d+):(.+)\]:\s?(.+)(\r?)/g, `\n<div class="number-item" data-label="$3"><a href="$5" class="$2" alt="$4">$4</a></div>\r`)
// standard href
.replace(/(\n\s*)(href)\[(.+)\]:\s?(.+)(\r?)/g, `\n<a href="$4" class="$2" alt="$3">$3</a>\r`)
.replace(/(\n\s*)(audio)\[(tts)\]:\s?(.+)/g, `$1<div class="item $2 $3"><span class="label">$2</span><span class="value"><audio src="$4" controls autoplay></audio></span></div>`)
// audio feature
.replace(/(\n\s*)(audio):\s?(.+)/g, `$1<div class="item $2"><span class="label">$2</span><span class="value"><audio class="$2-player" src="$3" controls></audio></span></div>`)
.replace(/\[PRESS RETURN\]/g, '<div class="press_return"><button class="btn return" title="Press Return" data-cloudbtn="">Press Return</button><div>')
.replace(/(\n\s*)(l):\s?(.+)/gi, `$1<div class="line">$3</div>`)
.replace(/(\n\s*)(p|h1|h2|h3|h4|h5|article)\[speak\:(.+)?]:\s?(.+)/gi, `$1<$2><button class="btn speak" alt="Speak" data-cmd="#voice say:$3 $4">💬</button> $4</$2>`)
.replace(/(\n\s*)(p|div|span|h1|h2|h3|h4|h5|article|section|br):\s?(.+)/gi, `$1<$2>$3</$2>`)
.replace(/(\n\s*)(\w+):\s(.+)/gi, `$1<div class="item $2"><span class="label">$2</span><span class="value">$3</span></div>`)
// strong format
.trim();
// set the container
// if (this.container) this.html = `<div class="${this.container}">${this.html}</div>`;
// .replace(/(.+)\n\n/g, '<p>$1</p>')
// .replace(/(.+)\n/g, '<div class="line">$1</div>')
// .replace(/\n/g, '<div class="line br"></div>')
// .replace(/\n\r?/g, '<div class="line"></div>');
// .replace(/(.*)/g, '<div class="line">$1</div>');
// Reference to https://github.com/sindresorhus/ansi-regex
let cssStyle = [];
if (this.vars.bgcolor) cssStyle.push(`--browser-item-bgcolor:${this.vars.bgcolor.value}`);
if (this.vars.bg) cssStyle.push(`--browser-item-background: url(${this.vars.bg.value})`);
if (this.vars.color) cssStyle.push(`--browser-item-color: ${this.vars.color.value}`);
if (this.vars.highlight) cssStyle.push(`--browser-item-highlight: ${this.vars.highlight.value}`);
if (this.vars.shadow) cssStyle.push(`--browser-item-shadow: ${this.vars.shadow.value}`);
if (this.vars.size) cssStyle.push(`--browser-item-size: ${this.vars.size.value}`);
if (cssStyle.length) {
this.html = `<section class="cover" style="${cssStyle.join(';')}">${this.html}</section>`;
}
return this.html;
}
/***********
func: _extractVars
describe: Recursive function to extract variables from a text string.
params: text - text string to extract variables from.
***********/
_extractVars() {
if (!this.text) return false;
const reggie = this.text.match(/\n\s*(\#|\@|\$)(.+)\s?=\s?(.+)/);
if (!reggie) return;
this.vars[reggie[2].trim()] = {
type: reggie[1].trim(),
name: reggie[2].trim(),
value: reggie[3].trim(),
}
this.text = this.text.replace(reggie[0], `\nvar[${reggie[1].trim()}${reggie[2].trim()}]:${reggie[3].trim()}\r`);
return this._extractVars();
}
/***********
func: getVars
params: text - initial text string to get variables from.
describe: this is a helper function to recursive _extractVars.
***********/
getVars(opts) {
if (!this.text) return false;
const {id, q} = opts;
const {client, agent, data} = q;
const {colors} = agent.prompt;
const _profile = [];
_profile.push(`::begin:profile`);
for (let x in agent.profile) {
_profile.push(`${x}: ${agent.profile[x]}`);
}
_profile.push(`::end:profile\n`);
const profile = _profile.join('\n');
this.text = this.text.replace(/\{\{id\}\}/g, id)
.replace(/\{\{today\}\}/g, formatDate(Date.now(), 'long', true))
.replace(/\{\{client\.(\w+)\}\}/g, (match,token) => {
return client[token] || false;
}).replace(/\{\{agent\.(\w+)\}\}/g, (match,token) => {
return agent[token] || false;
}).replace(/\{\{profile\.(\w+)\}\}/g, (match,token) => {
return agent.profile[token] || false;
}).replace(/\{\{prompt\.(\w+)\}\}/g, (match,token) => {
return agent.prompt[token] || false;
}).replace(/\{\{profile\}\}/g, (match,token) => {
return profile || false;
});
return this._extractVars();
}
/***********
func: extractTalk
params: text - text string to extract talk triggers from.
describe: extract talk functions from a text string to communication with other agents/deva.
***********/
_extractTalk() {
if (!this.text) return false;
const id = this.uid();
const reggie = (/(\n\s*)talk:\s?(.+)/i).exec(this.text);
if (!reggie) return;
const placeholder = `{{${id}}}`;
this.talk.push({
id,
placeholder,
value: reggie[2],
});
this.text = this.text.replace(reggie[0], '\n' + placeholder)
return this._extractTalk();
}
/***********
func: getTalk
params: text - text string to parse the talk triggers from.
describe: Helper function to initiate recursive _extractTalk.
***********/
getTalk() {
if (!this.text) return false;
return this._extractTalk();
}
}
export default (opts) => {
const {meta, text, client, agent} = opts.q;
const key = agent ? agent.key : client.key;
const parser = new Parser({
id: opts.id,
key,
cmd: meta.params.shift(),
container: meta.params.length ? meta.params.join(' ') : false,
params: meta.params,
text: text.replace(/(\n)(\s*)(\W|\b)/g, '$1$3'),
client,
agent,
});
parser.getVars(opts);
parser.getTalk();
return {
text: parser.text.replace(/\\(#|@|$)/g, `$1`),
html: parser.formatHTML(),
data: {
id: parser.id,
params: parser.params,
vars: parser.vars,
talk: parser.talk,
},
}
}