fix(basic.gblib): update ChatServices.ts #420

Closed
eltociear wants to merge 2540 commits from patch-1 into main
13 changed files with 512 additions and 172 deletions
Showing only changes of commit a9ce03b353 - Show all commits

BIN
blank.docx Normal file

Binary file not shown.

BIN
blank.xlsx Normal file

Binary file not shown.

View file

@ -6,6 +6,7 @@ import { exec } from 'child_process';
import pjson from './package.json' assert { type: 'json' };
// Displays version of Node JS being used at runtime and others attributes.
console.log(``);
console.log(``);
console.log(` █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® `);

View file

@ -559,7 +559,7 @@ export class DialogKeywords {
// }
let { min, user, params } = await DialogKeywords.getProcessInfo(pid);
const sec = new SecService();
await sec.setParam(user.userId, name, value);
await sec.setParam(user.userId, name , value);
GBLog.info(`BASIC: ${name} = ${value} (botId: ${min.botId})`);
return { min, user, params };
}

View file

@ -137,41 +137,13 @@ export class GBVMService extends GBService {
GBLogEx.info(min, `BASIC: Installing .gbdialog node_modules for ${min.botId}...`);
const npmPath = urlJoin(process.env.PWD, 'node_modules', '.bin', 'npm');
child_process.execSync(`${npmPath} install`, { cwd: folder });
// // Hacks push-rpc to put timeout.
// const inject1 = `
// const { AbortController } = require("node-abort-controller");
// var controller_1 = new AbortController();
// var signal = controller_1.signal;
// setTimeout(function () { controller_1.abort(); }, 24 * 60 * 60 * 1000);`;
// const inject2 = `signal: signal,`;
// const js = Path.join(process.env.PWD, folder, 'node_modules/@push-rpc/http/dist/client.js');
// lineReplace({
// file: js,
// line: 75,
// text: inject1,
// addNewLine: true,
// callback: ({ file, line, text, replacedText, error }) => {
// lineReplace({
// file: js,
// line: 82,
// text: inject2,
// addNewLine: true,
// callback: ({ file, line, text, replacedText, error }) => {
// GBLogEx.info(min, `BASIC: Patching node_modules for ${min.botId} done.`);
// }
// });
// }
// });
}
// Hot swap for .vbs files.
const fullFilename = urlJoin(folder, filename);
if (process.env.DEV_HOTSWAP) {
Fs.watchFile(fullFilename, async () => {
await this.translateBASIC(fullFilename, mainName, min);
await this.translateBASIC(fullFilename, min);
const parsedCode: string = Fs.readFileSync(jsfile, 'utf8');
min.sandBoxMap[mainName.toLowerCase().trim()] = parsedCode;
});
@ -184,17 +156,17 @@ export class GBVMService extends GBService {
const jsStat = Fs.statSync(jsfile);
const interval = 30000; // If compiled is older 30 seconds, then recompile.
if (compiledAt.isFile() && compiledAt['mtimeMs'] > jsStat['mtimeMs'] + interval) {
await this.translateBASIC(fullFilename, mainName, min);
await this.translateBASIC(fullFilename, min);
}
} else {
await this.translateBASIC(fullFilename, mainName, min);
await this.translateBASIC(fullFilename, min);
}
const parsedCode: string = Fs.readFileSync(jsfile, 'utf8');
min.sandBoxMap[mainName.toLowerCase().trim()] = parsedCode;
return filename;
}
public async translateBASIC(filename: any, mainName: string, min: GBMinInstance) {
public async translateBASIC(filename: any, min: GBMinInstance) {
// Converts General Bots BASIC into regular VBS
let basicCode: string = Fs.readFileSync(filename, 'utf8');
@ -264,9 +236,11 @@ export class GBVMService extends GBService {
let list = this.list;
let httpUsername = this.httpUsername;
let httpPs = this.httpPs;
let page = null;
let today = this.today;
let now = this.now;
let page = null;
let files = [];
let col = 1;
// Transfers NLP auto variables into global object.
@ -354,15 +328,22 @@ export class GBVMService extends GBService {
const map = {};
for (let i = 1; i <= lines.length; i++) {
let line = lines[i - 1];
// Remove lines before statments.
line = line.replace(/^\s*\d+\s*/gi,'');
for (let j = 0; j < keywords.length; j++) {
lines[i - 1] = lines[i - 1].replace(keywords[j][0], keywords[j][1]);
line = line.replace(keywords[j][0], keywords[j][1]);
}
// Add additional lines returned from replacement.
let add = lines[i - 1].split(/\r\n|\r|\n/).length;
let add = line.split(/\r\n|\r|\n/).length;
current = current + (add ? add : 0);
map[i] = current;
lines[i - 1] = line;
}
code = `${lines.join('\n')}\n`;
@ -419,6 +400,7 @@ export class GBVMService extends GBService {
};
const dk = new DialogKeywords();
const sys = new SystemKeywords();
await dk.setFilter ({pid: pid, value: null });
sandbox['variables'] = variables;
sandbox['id'] = sys.getRandomId();

View file

@ -32,7 +32,9 @@
'use strict';
import { GBVMService } from "./GBVMService.js";
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js';
import { GBVMService } from './GBVMService.js';
import Path from 'path';
/**
* Image processing services of conversation to be called by BASIC.
@ -46,7 +48,7 @@ export class KeywordsExpressions {
if (accum.isConcatting) {
accum.soFar[accum.soFar.length - 1] += ',' + curr;
} else {
if(curr===""){
if (curr === '') {
curr = null;
}
accum.soFar.push(curr);
@ -67,15 +69,20 @@ export class KeywordsExpressions {
names.forEach(name => {
let value = items[i];
i++;
json = `${json} "${name}": ${value} ${names.length == i ? '' : ','}`;
json = `${json} "${name}": ${value === undefined ? null : value} ${names.length == i ? '' : ','}`;
});
json = `${json}`;
return json;
};
public static isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
/**
* Returns the list of BASIC keyword and their JS match.
* Based on https://github.com/uweg/vbscript-to-typescript.
*/
public static getKeywords() {
// Keywords from General Bots BASIC.
@ -91,14 +98,134 @@ export class KeywordsExpressions {
return result;
};
keywords[i++] = [
/^\s*INPUT(.*)/gim,
($0, $1, $2) => {
let separator;
if ($1.indexOf(',') > -1){
separator = ',';
}
else if ($1.indexOf(';') > -1){
separator = ';';
}
let parts;
if ( separator && (parts = $1.split(separator)) && parts.length > 1){
return `
TALK ${parts[0]}
HEAR ${parts[1]}`;
}
else
{
return `
HEAR ${$1}`;
}
}
];
keywords[i++] = [
/^\s*WRITE(.*)/gim,
($0, $1, $2) => {
return `PRINT${$1}`;
}
];
keywords[i++] = [/^\s*REM.*/gim, ''];
keywords[i++] = [/^\s*CLOSE.*/gim, ''];
// Always autoclose keyword.
keywords[i++] = [/^\s*CLOSE.*/gim, ''];
keywords[i++] = [/^\s*\'.*/gim, ''];
keywords[i++] = [
/^\s*PRINT ([\s\S]*)/gim,
($0, $1, $2) => {
let sessionName;
let kind = null;
let pos;
$1 = $1.trim();
if ($1.substr(0, 1) === '#') {
let sessionName = $1.substr(1, $1.indexOf(',') - 1);
$1 = $1.replace(/\; \"\,\"/gi, '');
$1 = $1.substr($1.indexOf(',') + 1);
let separator;
if ($1.indexOf(',') > -1){
separator = ',';
}
else if ($1.indexOf(';') > -1){
separator = ';';
}
let items;
if (separator && (items = $1.split(separator)) && items.length > 1) {
return `await sys.save({pid: pid, file: files[${sessionName}], args:[${items.join(',')}]})`;
} else {
return `await sys.set({pid: pid, file: files[${sessionName}], address: col++, name: "${items[0]}", value: ${items[0]}})`;
}
} else {
return `await dk.talk({pid: pid, text: ${$1}})`;
}
}
];
keywords[i++] = [
/^\s*open([\s\S]*)/gim,
($0, $1, $2) => {
let sessionName;
let kind = null;
let pos;
$1 = $1.replace('FOR APPEND', '');
$1 = $1.replace('FOR OUTPUT', '');
if ((pos = $1.match(/\s*AS\s*\#/gi))) {
kind = 'AS';
} else if ((pos = $1.match(/\s*WITH\s*\#/gi))) {
kind = 'WITH';
}
if (pos) {
let part = $1.substr($1.lastIndexOf(pos[0]));
sessionName = `${part.substr(part.indexOf('#') + 1)}`;
$1 = $1.substr(0, $1.lastIndexOf(pos[0]));
}
$1 = $1.trim();
if (!$1.startsWith('"') && !$1.startsWith("'")) {
$1 = `"${$1}"`;
}
const params = this.getParams($1, ['url', 'username', 'password']);
// Checks if it is opening a file or a webpage.
if (kind === 'AS' && KeywordsExpressions.isNumber(sessionName)) {
const jParams = JSON.parse(`{${params}}`);
const filename = `${jParams.url.substr(0, jParams.url.lastIndexOf("."))}.xlsx`;
let code =
`
col = 1
await sys.save({pid: pid,file: "${filename}", args: [id] })
await dk.setFilter ({pid: pid, value: "id=" + id })
files[${sessionName}] = "${filename}"
`;
return code;
} else {
sessionName = `"${sessionName}"`;
kind = `"${kind}"`;
return `page = await wa.openPage({pid: pid, handle: page, sessionKind: ${kind}, sessionName: ${sessionName}, ${params}})`;
}
}
];
keywords[i++] = [
/^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*SELECT\s*(.*)/gim,
($0, $1, $2) => {
let tableName = /\s*FROM\s*(\w+)/.exec($2)[1];
let tableName = /\s*FROM\s*(\w+\$*)/.exec($2)[1];
let sql = `SELECT ${$2}`.replace(tableName, '?');
return `${$1} = await sys.executeSQL({pid: pid, data:${$1}, sql:"${sql}", tableName:"${tableName}"})\n`;
}
@ -114,8 +241,6 @@ export class KeywordsExpressions {
}
];
// Based on https://github.com/uweg/vbscript-to-typescript.
keywords[i++] = [/^\s*else(?!{)/gim, '}\nelse {'];
keywords[i++] = [/^\s*select case +(.*)/gim, 'switch ($1) {'];
@ -141,31 +266,43 @@ export class KeywordsExpressions {
keywords[i++] = [/^\s*loop *$/gim, '}'];
keywords[i++] = [
/^\s*open\s*(.*)/gim,
/^\s*open([\s\S]*)/gim,
($0, $1, $2) => {
let sessionName;
let kind = null;
let pos;
if (pos = $1.match(/\s*AS\s*\#/)) {
kind = '"AS"';
} else if (pos = $1.match(/\s*WITH\s*\#/)) {
kind = '"WITH"';
$1 = $1.replace(' FOR APPEND', '');
if ((pos = $1.match(/\s*AS\s*\#/))) {
kind = 'AS';
} else if ((pos = $1.match(/\s*WITH\s*\#/))) {
kind = 'WITH';
}
if (pos) {
let part = $1.substr($1.lastIndexOf(pos[0]));
sessionName = `"${part.substr(part.indexOf('#') + 1)}"`;
sessionName = `${part.substr(part.indexOf('#') + 1)}`;
$1 = $1.substr(0, $1.lastIndexOf(pos[0]));
}
$1 = $1.trim();
if (!$1.startsWith('"') && !$1.startsWith("'")) {
$1 = `"${$1}"`;
}
const params = this.getParams($1, ['url', 'username', 'password']);
// Checks if it is opening a file or a webpage.
if (kind === 'AS' && KeywordsExpressions.isNumber(sessionName)) {
const jParams = JSON.parse(`{${params}}`);
const filename = `${Path.basename(jParams.url, 'txt')}xlsx`;
return `files[${sessionName}] = "${filename}"`;
} else {
sessionName = `"${sessionName}"`;
kind = `"${kind}"`;
return `page = await wa.openPage({pid: pid, handle: page, sessionKind: ${kind}, sessionName: ${sessionName}, ${params}})`;
}
}
];
keywords[i++] = [
@ -176,112 +313,112 @@ export class KeywordsExpressions {
];
keywords[i++] = [
/^\s*hear (\w+) as (\w+( \w+)*.xlsx)/gim,
/^\s*hear (\w+\$*) as (\w+( \w+)*.xlsx)/gim,
($0, $1, $2) => {
return `${$1} = await dk.hear({pid: pid, kind:"sheet", arg: "${$2}"})`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*login/gim,
/^\s*hear (\w+\$*) as\s*login/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"login"})`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*email/gim,
/^\s*hear (\w+\$*) as\s*email/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"email"})`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*integer/gim,
/^\s*hear (\w+\$*) as\s*integer/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"integer"})`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*file/gim,
/^\s*hear (\w+\$*) as\s*file/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"file"})`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*boolean/gim,
/^\s*hear (\w+\$*) as\s*boolean/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"boolean"})`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*name/gim,
/^\s*hear (\w+\$*) as\s*name/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"name"})`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*date/gim,
/^\s*hear (\w+\$*) as\s*date/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"date"})`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*hour/gim,
/^\s*hear (\w+\$*) as\s*hour/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"hour"})`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*phone/gim,
/^\s*hear (\w+\$*) as\s*phone/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"phone"})`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*money/gim,
/^\s*hear (\w+\$*) as\s*money/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"money")}`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*qrcode/gim,
/^\s*hear (\w+\$*) as\s*qrcode/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"qrcode")}`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*language/gim,
/^\s*hear (\w+\$*) as\s*language/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"language")}`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*zipcode/gim,
/^\s*hear (\w+\$*) as\s*zipcode/gim,
($0, $1) => {
return `${$1} = await dk.hear({pid: pid, kind:"zipcode")}`;
}
];
keywords[i++] = [
/^\s*hear (\w+) as\s*(.*)/gim,
/^\s*hear (\w+\$*) as\s*(.*)/gim,
($0, $1, $2) => {
return `${$1} = await dk.hear({pid: pid, kind:"menu", args: [${$2}]})`;
}
];
keywords[i++] = [
/^\s*(hear)\s*(\w+)/gim,
/^\s*(hear)\s*(\w+\$*)/gim,
($0, $1, $2) => {
return `${$2} = await dk.hear({pid: pid})`;
}
@ -345,21 +482,21 @@ export class KeywordsExpressions {
];
keywords[i++] = [
/^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*sort\s*(\w+)\s*by(.*)/gim,
/^\s*((?:[a-z]+.?)(?:(?:\w+).)(?:\w+)*)\s*=\s*sort\s*(\w+\$*)\s*by(.*)/gim,
($0, $1, $2, $3) => {
return `${$1} = await sys.sortBy({pid: pid, array: ${$2}, memberName: "${$3}"})`;
}
];
keywords[i++] = [
/^\s*see\s*text\s*of\s*(\w+)\s*as\s*(\w+)\s*/gim,
/^\s*see\s*text\s*of\s*(\w+\$*)\s*as\s*(\w+\$*)\s*/gim,
($0, $1, $2, $3) => {
return `${$2} = await sys.seeText({pid: pid, url: ${$1})`;
}
];
keywords[i++] = [
/^\s*see\s*caption\s*of\s*(\w+)\s*as(.*)/gim,
/^\s*see\s*caption\s*of\s*(\w+\$*)\s*as(.*)/gim,
($0, $1, $2, $3) => {
return `${$2} = await sys.seeCaption({pid: pid, url: ${$1})`;
}
@ -618,7 +755,6 @@ export class KeywordsExpressions {
// Uses auto quote if this is a frase with more then one word.
if (/\s/.test($3) && $3.substr(0, 1) !== '"') {
$3 = `"${$3}"`;
}
return `await dk.talk ({pid: pid, text: ${$3}})`;
@ -772,7 +908,7 @@ export class KeywordsExpressions {
];
keywords[i++] = [
/^\s*save\s*(\w+)\s*as\s*(.*)/gim,
/^\s*save\s*(\w+\$*)\s*as\s*(.*)/gim,
($0, $1, $2, $3) => {
return `await sys.saveFile({pid: pid, file: ${$2}, data: ${$1}})`;
}
@ -781,10 +917,10 @@ export class KeywordsExpressions {
keywords[i++] = [
/^\s*(save)(\s*)(.*\.xlsx)(.*)/gim,
($0, $1, $2, $3, $4) => {
$3 = $3.replace (/\'/g, "")
$3 = $3.replace (/\"/g, "")
$4 = $4.substr(2)
return `await sys.save({pid: pid,file: "${$3}" , args: [${$4}]})`;
$3 = $3.replace(/\'/g, '');
$3 = $3.replace(/\"/g, '');
$4 = $4.substr(2);
return `await sys.save({pid: pid, file: "${$3}", args: [${$4}]})`;
}
];

View file

@ -58,6 +58,7 @@ import DynamicsWebApi from 'dynamics-web-api';
import * as MSAL from '@azure/msal-node';
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService.js';
import { WebAutomationServices } from './WebAutomationServices.js';
import { KeywordsExpressions } from './KeywordsExpressions.js';
/**
* @fileoverview General Bots server core.
@ -471,7 +472,7 @@ export class SystemKeywords {
* @example SET page, "elementHTMLSelector", "text"
*
*/
public async set({ pid, handle, file, address, value }): Promise<any> {
public async set({ pid, handle, file, address, value, name = null }): Promise<any> {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
// Handles calls for HTML stuff
@ -485,14 +486,6 @@ export class SystemKeywords {
// TODO: Add a semaphore between FILTER and SET.
// Processes FILTER option to ensure parallel SET calls.
const filter = await DialogKeywords.getOption({ pid, name });
if (filter) {
const row = this.find({ pid, handle: null, args: [filter] });
address += row['line'];
}
// Handles calls for BASIC persistence on sheet files.
GBLog.info(`BASIC: Defining '${address}' in '${file}' to '${value}' (SET). `);
@ -501,16 +494,53 @@ export class SystemKeywords {
const botId = min.instance.botId;
const path = DialogKeywords.getGBAIPath(botId, 'gbdata');
let document = await this.internalGetDocument(client, baseUrl, path, file);
let sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
let body = { values: [[]] };
// Processes FILTER option to ensure parallel SET calls.
const filter = await DialogKeywords.getOption({ pid, name: 'filter' });
let titleAddress;
if (filter) {
// Transforms address number (col index) to letter based.
// Eg.: REM This is A column and index automatically specified by filter.
// SET file.xlsx, 1, 4000
if (KeywordsExpressions.isNumber(address)) {
address = `${this.numberToLetters(address)}`;
titleAddress = `${address}1:${address}1`;
}
// Processes SET FILTER directive to calculate address.
body.values[0][0] = 'id';
const addressId = 'A1:A1';
await client
.api(
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${addressId}')`
)
.patch(body);
const row = await this.find({ pid, handle: null, args: [file, filter] });
if (row) {
address += row['line']; // Eg.: "A" + 1 = "A1".
}
}
address = address.indexOf(':') !== -1 ? address : address + ':' + address;
let document = await this.internalGetDocument(client, baseUrl, path, file);
if (titleAddress) {
body.values[0][0] = name.trim().replace(/[^a-zA-Z]/gi, '');
await client
.api(
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${titleAddress}')`
)
.patch(body);
}
let body = { values: [[]] };
body.values[0][0] = value;
let sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
await client
.api(
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${address}')`
@ -529,7 +559,10 @@ export class SystemKeywords {
});
if (!documents || documents.length === 0) {
throw `File '${file}' specified on GBasic command not found. Check the .gbdata or the .gbdialog associated.`;
throw new Error(
`File '${file}' specified on GBasic command not found. Check the .gbdata or the .gbdialog associated.`,
{ cause: 404 }
);
}
return documents[0];
@ -575,36 +608,84 @@ export class SystemKeywords {
*/
public async save({ pid, file, args }): Promise<any> {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
args.shift();
GBLog.info(`BASIC: Saving '${file}' (SAVE). Args: ${args.join(',')}.`);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const botId = min.instance.botId;
const path = DialogKeywords.getGBAIPath(botId, 'gbdata');
let document = await this.internalGetDocument(client, baseUrl, path, file);
let sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
let sheets;
let document;
try {
document = await this.internalGetDocument(client, baseUrl, path, file);
sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
} catch (e) {
if (e.cause === 404) {
// Creates the file.
const blank = Path.join(process.env.PWD, 'blank.xlsx');
const data = Fs.readFileSync(blank);
await client.api(`${baseUrl}/drive/root:/${path}/${file}:/content`).put(data);
// Tries to open again.
document = await this.internalGetDocument(client, baseUrl, path, file);
sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
} else {
throw e;
}
}
let address;
let body = { values: [[]] };
// Processes FILTER option to ensure parallel SET calls.
const filter = await DialogKeywords.getOption({ pid, name: 'filter' });
if (filter) {
// Creates id row.
body.values[0][0] = 'id';
const addressId = 'A1:A1';
await client
.api(
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${addressId}')`
)
.patch(body);
body.values[0][0] = undefined ;
// FINDs the filtered row to be updated.
const row = await this.find({ pid, handle: null, args: [file, filter] });
if (row) {
address = `A${row['line']}:${this.numberToLetters(args.length)}${row['line']}`;
}
}
// Editing or saving detection.
if (!address) {
await client
.api(
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='A2:DX2')/insert`
)
.post({});
if (args.length > 128) {
throw `File '${file}' has a SAVE call with more than 128 arguments. Check the .gbdialog associated.`;
address = `A2:${this.numberToLetters(args.length - 1)}2`;
}
let body = { values: [[]] };
// Fills rows object to call sheet API.
const address = `A2:${this.numberToLetters(args.length - 1)}2`;
for (let index = 0; index < args.length; index++) {
let value = args[index];
if (value && (await this.isValidDate({ pid, dt: value }))) {
value = `'${value}`;
}
body.values[0][index] = value;
}
// If filter is defined, skips id column.
body.values[0][filter ? index + 1 : index] = value;
}
await client
.api(
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${address}')`
@ -675,11 +756,7 @@ export class SystemKeywords {
}
public async isValidNumber({ pid, number }) {
const { min, user } = await DialogKeywords.getProcessInfo(pid);
if (number === '') {
return false;
}
return !isNaN(number);
return KeywordsExpressions.isNumber(number);
}
public isValidHour({ pid, value }) {
@ -1179,6 +1256,33 @@ export class SystemKeywords {
await client.api(`https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/invite`).post(body);
}
public async internalCreateDocument(min, path, content) {
GBLog.info(`BASIC: CREATE DOCUMENT '${path}...'`);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const gbaiName = DialogKeywords.getGBAIPath(min.botId);
const tmpDocx = urlJoin(gbaiName, path);
// Templates a blank {content} tag inside the blank.docx.
const blank = Path.join(process.env.PWD, 'blank.docx');
let buf = Fs.readFileSync(blank);
let zip = new PizZip(buf);
let doc = new Docxtemplater();
doc.setOptions({ linebreaks: true });
doc.loadZip(zip);
doc.setData({ content: content }).render();
buf = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE' });
// Performs the upload.
await client.api(`${baseUrl}/drive/root:/${tmpDocx}:/content`).put(buf);
}
public async createDocument({ pid, path, content }) {
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
this.internalCreateDocument(min, path, content);
}
/**
* Copies a drive file from a place to another .
*
@ -1224,8 +1328,9 @@ export class SystemKeywords {
parentReference: { driveId: folder.parentReference.driveId, id: folder.id },
name: `${Path.basename(dest)}`
};
return await client.api(`${baseUrl}/drive/items/${srcFile.id}/copy`).post(destFile);
const file = await client.api(`${baseUrl}/drive/items/${srcFile.id}/copy`).post(destFile);
GBLog.info(`BASIC: FINISHED COPY '${src}' to '${dest}'`);
return file;
} catch (error) {
if (error.code === 'itemNotFound') {
GBLog.info(`BASIC: COPY source file not found: ${srcPath}.`);
@ -1234,7 +1339,6 @@ export class SystemKeywords {
}
throw error;
}
GBLog.info(`BASIC: FINISHED COPY '${src}' to '${dest}'`);
}
/**
@ -1468,10 +1572,6 @@ export class SystemKeywords {
localName = Path.join('work', gbaiName, 'cache', `tmp${GBAdminService.getRndReadableIdentifier()}.docx`);
Fs.writeFileSync(localName, buf, { encoding: null });
// Loads the file as binary content.
let zip = new PizZip(buf);
// Replace image path on all elements of data.
const images = [];
@ -1537,6 +1637,9 @@ export class SystemKeywords {
}
};
// Loads the file as binary content.
let zip = new PizZip(buf);
let doc = new Docxtemplater();
doc.setOptions({ paragraphLoop: true, linebreaks: true });
doc.loadZip(zip);

View file

@ -166,7 +166,6 @@ export class GBMinService {
// Calls mountBot event to all bots.
let i = 1;
if (instances.length > 1) {
this.bar1 = new cliProgress.SingleBar(
{
@ -179,19 +178,20 @@ export class GBMinService {
this.bar1.start(instances.length, i, { botId: 'Boot' });
}
await CollectionUtil.asyncForEach(instances, (async instance => {
await CollectionUtil.asyncForEach(
instances,
(async instance => {
try {
await this['mountBot'](instance);
} catch (error) {
GBLog.error(`Error mounting bot ${instance.botId}: ${error.message}\n${error.stack}`);
}
finally {
} finally {
if (this.bar1) {
this.bar1.update(i++, { botId: instance.botId });
}
}
}).bind(this));
}).bind(this)
);
if (this.bar1) {
this.bar1.stop();
@ -200,10 +200,10 @@ export class GBMinService {
pingSendTimeout: null,
keepAliveTimeout: null,
listeners: {
unsubscribed(subscriptions: number): void { },
subscribed(subscriptions: number): void { },
disconnected(remoteId: string, connections: number): void { },
connected(remoteId: string, connections: number): void { },
unsubscribed(subscriptions: number): void {},
subscribed(subscriptions: number): void {},
disconnected(remoteId: string, connections: number): void {},
connected(remoteId: string, connections: number): void {},
messageIn(...params): void {
GBLogEx.info(0, '[IN] ' + params);
},
@ -237,8 +237,6 @@ export class GBMinService {
GBLogEx.info(0, 'API RPC HTTP Server started.');
// // Loads schedules.
// GBLog.info(`Preparing SET SCHEDULE dialog calls...`);
@ -286,7 +284,7 @@ export class GBMinService {
/**
* Unmounts the bot web site (default.gbui) secure domain, if any.
*/
public async unloadDomain(instance: IGBInstance) { }
public async unloadDomain(instance: IGBInstance) {}
/**
* Mount the instance by creating an BOT Framework bot object,
@ -331,7 +329,7 @@ export class GBMinService {
const gbai = DialogKeywords.getGBAIPath(min.botId);
let dir = `work/${gbai}/cache`;
const botId = gbai.replace(/\.[^/.]+$/, "");
const botId = gbai.replace(/\.[^/.]+$/, '');
if (!Fs.existsSync(dir)) {
mkdirp.sync(dir);
@ -563,7 +561,8 @@ export class GBMinService {
min.instance.authenticatorTenant,
'/oauth2/authorize'
);
authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${min.instance.marketplaceId
authorizationUrl = `${authorizationUrl}?response_type=code&client_id=${
min.instance.marketplaceId
}&redirect_uri=${urlJoin(min.instance.botEndpoint, min.instance.botId, 'token')}`;
GBLog.info(`HandleOAuthRequests: ${authorizationUrl}.`);
res.redirect(authorizationUrl);
@ -1065,7 +1064,8 @@ export class GBMinService {
await this.processEventActivity(min, user, context, step);
}
} catch (error) {
const msg = `ERROR: ${error.message} ${error.error ? error.error.body : ''} ${error.error ? (error.error.stack ? error.error.stack : '') : ''
const msg = `ERROR: ${error.message} ${error.error ? error.error.body : ''} ${
error.error ? (error.error.stack ? error.error.stack : '') : ''
}`;
GBLog.error(msg);
@ -1085,8 +1085,7 @@ export class GBMinService {
if (error.code === 401) {
GBLog.error('Calling processActivity due to Signing Key could not be retrieved error.');
await adapter['processActivity'](req, res, handler);
}
else {
} else {
throw error;
}
}
@ -1275,7 +1274,9 @@ export class GBMinService {
// Files in .gbdialog can be called directly by typing its name normalized into JS .
const isVMCall = Object.keys(min.scriptMap).find(key => min.scriptMap[key] === context.activity.text) !== undefined;
if (isVMCall) {
if (/create dialog|creative dialog|create a dialog|criar diálogo|criar diálogo/gi.test(context.activity.text)) {
await step.beginDialog('/dialog');
} else if (isVMCall) {
await GBVMService.callVM(context.activity.text, min, step, user, this.deployer, false);
} else if (context.activity.text.charAt(0) === '/') {
const text = context.activity.text;

View file

@ -326,7 +326,7 @@ class GBUIApp extends React.Component {
ref={chat => {
this.chat = chat;
}}
locale={'pt-br'}
locale={'en-us'}
directLine={this.state.line}
webSpeechPonyfillFactory={window.WebChat.createCognitiveServicesSpeechServicesPonyfillFactory({
credentials: { authorizationToken: token, region: 'westus' }

View file

@ -50,6 +50,9 @@ import { GBVMService } from '../../basic.gblib/services/GBVMService.js';
import { GBImporter } from '../../core.gbapp/services/GBImporterService.js';
import { GBDeployer } from '../../core.gbapp/services/GBDeployer.js';
import { GBConfigService } from '../../core.gbapp/services/GBConfigService.js';
import Fs from 'fs';
import urlJoin from 'url-join';
import { SystemKeywords } from '../../basic.gblib/services/SystemKeywords.js';
/**
* Dialog arguments.
@ -78,6 +81,7 @@ export class AskDialog extends IGBDialog {
min.dialogs.add(new WaterfallDialog('/answerEvent', AskDialog.getAnswerEventDialog(service, min)));
min.dialogs.add(new WaterfallDialog('/answer', AskDialog.getAnswerDialog(min, service)));
min.dialogs.add(new WaterfallDialog('/ask', AskDialog.getAskDialog(min)));
min.dialogs.add(new WaterfallDialog('/dialog', AskDialog.getLLVMDialog(min, service)));
}
private static getAskDialog(min: GBMinInstance) {
@ -234,7 +238,7 @@ export class AskDialog extends IGBDialog {
}
// TODO: https://github.com/GeneralBots/BotServer/issues/9
// else if (user.subjects && user.subjects.length > 0) {
// // ...second time running Search, now with no filter.
// // ..second time running Search, now with no filter.
// const resultsB = await service.ask(min.instance, text, searchScore, undefined);
@ -304,6 +308,7 @@ export class AskDialog extends IGBDialog {
}
const CHATGPT_TIMEOUT = 60 * 1000;
GBLog.info(`ChatGPT being used...`);
const response = await GBServer.globals.chatGPT.sendMessage(text, { timeoutMs: CHATGPT_TIMEOUT });
if (!response) {
@ -363,4 +368,77 @@ export class AskDialog extends IGBDialog {
}
];
}
private static getLLVMDialog(min: GBMinInstance, service: KBService) {
return [
async step => {
if (step.context.activity.channelId !== 'msteams' && process.env.ENABLE_AUTH) {
return await step.beginDialog('/auth');
} else {
return await step.next(step.options);
}
},
async step => {
return await min.conversationalService.prompt(min, step, 'Please, describe the dialog scene.');
},
async step => {
step.options.dialog = step.result;
return await min.conversationalService.prompt(min, step, 'How would you call this?');
},
async step => {
if (GBServer.globals.chatGPT) {
let input = `Write a BASIC program that ${step.options.dialog.toLowerCase()}. And does not explain.`;
await min.conversationalService.sendText(min, step, 'Thank you. The dialog is being written right now...');
const CHATGPT_TIMEOUT = 3 * 60 * 1000;
GBLog.info(`ChatGPT Code: ${input}`);
let response = await GBServer.globals.chatGPT.sendMessage(input, {
timeoutMs: CHATGPT_TIMEOUT
});
// Removes instructions, just code.
response = response.replace(/Copy code/gim, '\n');
let lines = response.split('\n')
let filteredLines = lines.filter(line => /\s*\d+\s*.*/.test(line))
response = filteredLines.join('\n');
// Gets dialog name and file handling
let dialogName = step.result.replace('.', '');
const docx = urlJoin(`${min.botId}.gbdialog`, `${dialogName}.docx`);
const sys = new SystemKeywords();
const document = await sys.internalCreateDocument(min, docx, response);
await service.addQA(min, dialogName, dialogName);
let message = `Waiting for publishing...`;
await min.conversationalService.sendText(min, step, message);
await step.replaceDialog('/publish', { confirm: true });
message = `Dialog is ready! Let's run:`;
await min.conversationalService.sendText(min, step, message);
let sec = new SecService();
const member = step.context.activity.from;
const user = await sec.ensureUser(
min.instance.instanceId,
member.id,
member.name,
'',
'web',
member.name,
null
);
await step.endDialog();
await GBVMService.callVM(dialogName.toLowerCase(),
min, step, user, this.deployer, false);
}
}
];
}
}

View file

@ -529,7 +529,7 @@ export class KBService implements IGBKBService {
const isBasic = answer.toLowerCase().startsWith('/basic');
if (/TALK\s*\".*\"/gi.test(answer) || isBasic) {
const code = isBasic ? answer.substr(6) : answer;
const path = DialogKeywords.getGBAIPath(min.botId,`gbdialog`);
const path = DialogKeywords.getGBAIPath(min.botId, `gbdialog`);
const scriptName = `tmp${GBAdminService.getRndReadableIdentifier()}.docx`;
const localName = Path.join('work', path, `${scriptName}`);
Fs.writeFileSync(localName, code, { encoding: null });
@ -604,17 +604,11 @@ export class KBService implements IGBKBService {
answer.content.endsWith('.xlsx')
) {
const path = DialogKeywords.getGBAIPath(min.botId, `gbkb`);
const doc = urlJoin(
GBServer.globals.publicAddress,
'kb',
path,
'assets',
answer.content
);
const doc = urlJoin(GBServer.globals.publicAddress, 'kb', path, 'assets', answer.content);
const url = `http://view.officeapps.live.com/op/view.aspx?src=${doc}`;
await this.playUrl(min, min.conversationalService, step, url, channel);
} else if (answer.content.endsWith('.pdf')) {
const path = DialogKeywords.getGBAIPath(min.botId,`gbkb`);
const path = DialogKeywords.getGBAIPath(min.botId, `gbkb`);
const url = urlJoin('kb', path, 'assets', answer.content);
await this.playUrl(min, min.conversationalService, step, url, channel);
} else if (answer.format === '.md') {
@ -627,6 +621,37 @@ export class KBService implements IGBKBService {
}
}
public async addQA(min, questionText, answerText) {
const pkg = await GuaribasPackage.findOne({
where: { instanceId: min.instance.instanceId }
});
const question = {
from: 'autodialog',
to: '',
subject1: '',
subject2: '',
subject3: '',
subject4: '',
content: questionText.replace(/["]+/g, ''),
instanceId: min.instance.instanceId,
skipIndex: false,
packageId: pkg.packageId
};
const answer = {
instanceId: min.instance.instanceId,
content: answerText,
format: '.txt',
media: null,
packageId: pkg.packageId,
prevId: 0
};
const a =await GuaribasAnswer.create(answer);
question['answerId'] = a.answerId;
const q = await GuaribasQuestion.create(question);
}
public async importKbPackage(
min: GBMinInstance,
localPath: string,
@ -692,9 +717,12 @@ export class KBService implements IGBKBService {
'cache',
`img-docx${GBAdminService.getRndReadableIdentifier()}.png`
);
const url = urlJoin(GBServer.globals.publicAddress,
DialogKeywords.getGBAIPath(instance.botId).replace(/\.[^/.]+$/, "")
, 'cache', Path.basename(localName));
const url = urlJoin(
GBServer.globals.publicAddress,
DialogKeywords.getGBAIPath(instance.botId).replace(/\.[^/.]+$/, ''),
'cache',
Path.basename(localName)
);
const buffer = await image.read();
Fs.writeFileSync(localName, buffer, { encoding: null });
return { src: url };
@ -965,7 +993,7 @@ export class KBService implements IGBKBService {
let category = categoryReg[1];
if (category === 'number') {
min['nerEngine'].addRegexEntity('number','pt', '/d+/gi');
min['nerEngine'].addRegexEntity('number', 'pt', '/d+/gi');
}
if (nameReg) {
let name = nameReg[1];
@ -996,13 +1024,8 @@ export class KBService implements IGBKBService {
GBLog.info(`[GBDeployer] Start Bot Server Side Rendering... ${localPath}`);
const html = await GBSSR.getHTML(min);
let path = DialogKeywords.getGBAIPath(min.botId,`gbui`);
path = Path.join(
process.env.PWD,
'work',
path,
'index.html'
);
let path = DialogKeywords.getGBAIPath(min.botId, `gbui`);
path = Path.join(process.env.PWD, 'work', path, 'index.html');
GBLogEx.info(min, `[GBDeployer] Saving SSR HTML in ${path}.`);
Fs.writeFileSync(path, html, 'utf8');

View file

@ -279,7 +279,7 @@ export class SecService extends GBService {
{
obj = {};
}
obj['name'] = value;
obj[name] = value;
user.params = JSON.stringify(obj);
return await user.save();
}

View file

@ -52,11 +52,13 @@ import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer.js';
import { GBImporter } from '../packages/core.gbapp/services/GBImporterService.js';
import { GBMinService } from '../packages/core.gbapp/services/GBMinService.js';
import auth from 'basic-auth';
import { ChatGPTAPIBrowser } from 'chatgpt';
import child_process from 'child_process';
import * as winston from 'winston-logs-display';
import { RootData } from './RootData.js';
import { GBSSR } from '../packages/core.gbapp/services/GBSSR.js';
import { Mutex } from 'async-mutex';
import { GBVMService } from '../packages/basic.gblib/services/GBVMService.js';
/**
* General Bots open-core entry point.
@ -215,6 +217,22 @@ export class GBServer {
GBServer.globals.minService = minService;
await minService.buildMin(instances);
if (process.env.OPENAI_EMAIL) {
if (!GBServer.globals.chatGPT) {
GBServer.globals.chatGPT = new ChatGPTAPIBrowser({
email: process.env.OPENAI_EMAIL,
password: process.env.OPENAI_PASSWORD,
markdown: false
});
await GBServer.globals.chatGPT.init();
}
}
// let s = new GBVMService();
// await s.translateBASIC('work/gptA.vbs', GBServer.globals.minBoot );
// await s.translateBASIC('work/gptB.vbs', GBServer.globals.minBoot );
// await s.translateBASIC('work/gptC.vbs', GBServer.globals.minBoot );
// process.exit(9);
if (process.env.ENABLE_WEBLOG) {
// If global log enabled, reorders transports adding web logging.
@ -257,8 +275,6 @@ export class GBServer {
})();
};
//
if (process.env.CERTIFICATE_PFX) {
const options1 = {
passphrase: process.env.CERTIFICATE_PASSPHRASE,