new(all): Auto import for logo, colors and website content.

This commit is contained in:
Rodrigo Rodriguez 2024-05-21 13:17:42 -03:00
parent 7fe50d95c4
commit e91c3a4e06
7 changed files with 176 additions and 65 deletions

2
.gitignore vendored
View file

@ -28,3 +28,5 @@ yarn-error.log
package-lock.json
yarn-lock.json
packages/saas.gbapp.zip
logo.svg
screenshot.png

View file

@ -59,9 +59,11 @@ import { GBHubSpotPackage } from '../../hubspot.gblib/index.js';
import open from 'open';
import ngrok from 'ngrok';
import Path from 'path';
import { file } from 'googleapis/build/src/apis/file/index.js';
import { GBUtil } from '../../../src/util.js';
import { GBLogEx } from './GBLogEx.js';
import { GBDeployer } from './GBDeployer.js';
import { SystemKeywords } from '../../basic.gblib/services/SystemKeywords.js';
import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js';
/**
* GBCoreService contains main logic for handling storage services related
@ -665,6 +667,50 @@ ENDPOINT_UPDATE=true
await installationDeployer.openStorageFirewall(group, serverName);
}
public async setConfig(min, name: string, value: any): Promise<any> {
// Handles calls for BASIC persistence on sheet files.
GBLog.info( `Defining Config.xlsx variable ${name}= '${value}'...`);
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
const maxLines = 512;
const file = "Config.xlsx";
const path = DialogKeywords.getGBAIPath(min.botId, `gbot`);;
let document = await (new SystemKeywords()).internalGetDocument(client, baseUrl, path, file);
// Creates workbook session that will be discarded.
let sheets = await client
.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`)
.get();
let results = await client
.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='A1:A${maxLines}')`)
.get();
const rows = results.text;
let address = '';
// Fills the row variable.
for (let i = 1; i <= rows.length; i++) {
let result = rows[i - 1][0];
if (result && result.toLowerCase() === name.toLowerCase()) {
address = `B${i}:B${i}`;
break;
}
}
let body = { values: [[]] };
body.values[0][0] = value;
await client
.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${address}')`)
.patch(body);
}

View file

@ -687,7 +687,13 @@ export class GBMinService {
paramLogoImageAlt: this.core.getParam(instance, 'Logo Image Alt', null),
paramLogoImageWidth: this.core.getParam(instance, 'Logo Image Width', null),
paramLogoImageHeight: this.core.getParam(instance, 'Logo Image Height', null),
paramLogoImageType: this.core.getParam(instance, 'Logo Image Type', null)
paramLogoImageType: this.core.getParam(instance, 'Logo Image Type', null),
logo: this.core.getParam(instance, 'Logo', null),
color1: this.core.getParam(instance, 'Color1', null),
color2: this.core.getParam(instance, 'Color2', null),
})
);
} else {

View file

@ -39,7 +39,7 @@ import GBBulletPlayer from './players/GBBulletPlayer.js';
import SidebarMenu from './components/SidebarMenu.js';
import SEO from './components/SEO.js';
import GBCss from './components/GBCss.js';
import { DirectLine} from 'botframework-directlinejs';
import { DirectLine } from 'botframework-directlinejs';
import { ConnectionStatus } from 'botframework-directlinejs';
import ReactWebChat from 'botframework-webchat';
import { UserAgentApplication } from 'msal';
@ -257,16 +257,16 @@ class GBUIApp extends React.Component {
);
break;
case 'multiurl':
playerComponent = (
<GBMultiUrlPlayer
app={this}
ref={player => {
this.player = player;
}}
/>
);
break;
case 'image':
playerComponent = (
<GBMultiUrlPlayer
app={this}
ref={player => {
this.player = player;
}}
/>
);
break;
case 'image':
playerComponent = (
<GBImagePlayer
app={this}
@ -305,18 +305,17 @@ class GBUIApp extends React.Component {
let chat = <div />;
let gbCss = <div />;
let seo = <div />;
let sideBar = (
<div className="sidebar">
<SidebarMenu chat={this.chat} instance={this.state.instanceClient} />
</div>
);
let sideBar = <div />;
if (this.state.line) {
if (this.state.instanceClient) {
let color1 = this.state.instanceClient.color1;
gbCss = <GBCss instance={this.state.instanceClient} />;
seo = <SEO instance={this.state.instanceClient} />;
const token = this.state.instanceClient.speechToken;
document.body.style.setProperty('background-color', this.state.instanceClient.color2, 'important');
chat = (
<ReactWebChat
ref={chat => {
@ -329,15 +328,25 @@ class GBUIApp extends React.Component {
})}
/>
);
sideBar = (
<div
className="sidebar"
ref={node => {
if (node) {
node.style.setProperty('background-color', this.state.instanceClient.color1, 'important');
}
}}
>
<SidebarMenu chat={this.chat} instance={this.state.instanceClient} />
</div>
);
}
}
if (!this.state.instanceClient) {
sideBar = '';
}
return (
<StaticContent>
{seo}
<div>
{gbCss}

View file

@ -49,7 +49,7 @@ class SideBarMenu extends React.Component {
<div className="tittleSideBarMenu">
<img
className="pragmatismoLogo"
src={"/themes/" + this.props.instance.theme + "/images/logo.png"}
src={this.props.instance.botId + "/cache/" + this.props.instance.logo}
alt="General Bots Logo" />
</div>

View file

@ -254,29 +254,28 @@ export class ChatServices {
public static async answerByGPT(
min: GBMinInstance,
user,
pid,
question: string,
searchScore: number,
subjects: GuaribasSubject[]
question: string, mode=null
) {
if (!process.env.OPENAI_API_KEY) {
return { answer: undefined, questionId: 0 };
}
const LLMMode = min.core.getParam(min.instance, 'Answer Mode', 'direct');
const LLMMode = mode??min.core.getParam(min.instance, 'Answer Mode', 'direct');
const docsContext = min['vectorStore'];
if (!this.memoryMap[user.userSystemId]) {
this.memoryMap[user.userSystemId] = new BufferWindowMemory({
returnMessages: true,
memoryKey: 'chat_history',
inputKey: 'input',
k: 2
});
const memory = new BufferWindowMemory({
returnMessages: true,
memoryKey: 'chat_history',
inputKey: 'input',
k: 2
});
if (user && !this.memoryMap[user.userSystemId]) {
this.memoryMap[user.userSystemId] = memory;
}
const memory = this.memoryMap[user.userSystemId];
const systemPrompt = this.userSystemPrompt[user.userSystemId];
const systemPrompt = user?this.userSystemPrompt[user.userSystemId]:'';
const model = new ChatOpenAI({
openAIApiKey: process.env.OPENAI_API_KEY,

View file

@ -53,7 +53,6 @@ import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { Document } from 'langchain/document';
import getColors from 'get-image-colors';
import {
GBDialogStep,
GBLog,
@ -377,7 +376,7 @@ export class KBService implements IGBKBService {
returnedScore: ${returnedScore} < required (searchScore): ${searchScore}`
);
return await ChatServices.answerByGPT(min, user, pid, query, searchScore, subjects);
return await ChatServices.answerByGPT(min, user, query);
}
public async getSubjectItems(instanceId: number, parentId: number): Promise<GuaribasSubject[]> {
@ -853,13 +852,13 @@ export class KBService implements IGBKBService {
}
async saveHtmlPage(min, url: string, page: Page): Promise<string | null> {
page.setCacheEnabled(false);
const response = await page.goto(url);
if (response.headers && response.status() === 200) {
if (response.headers && (response.status() === 200 || response.status() === 304)) {
const contentType = response.headers()['content-type'];
if (contentType && contentType.includes('text/html')) {
const buffer = await response.buffer();
const buffer = await page.content();
const urlObj = new URL(url);
const urlPath = urlObj.pathname.endsWith('/') ? urlObj.pathname.slice(0, -1) : urlObj.pathname; // Remove trailing slash if present
let filename = urlPath.split('/').pop() || 'index'; // Get the filename from the URL path or set it to 'index.html' as default
@ -882,12 +881,12 @@ export class KBService implements IGBKBService {
try {
if (
depth > maxDepth ||
(visited.has(url) ||
url.endsWith('.jpg') ||
url.endsWith('.pdf') ||
url.endsWith('.jpg') ||
url.endsWith('.png') ||
url.endsWith('.mp4'))
visited.has(url) ||
url.endsWith('.jpg') ||
url.endsWith('.pdf') ||
url.endsWith('.jpg') ||
url.endsWith('.png') ||
url.endsWith('.mp4')
) {
return [];
}
@ -904,6 +903,7 @@ export class KBService implements IGBKBService {
}
const currentDomain = new URL(page.url()).hostname;
let links = await page.evaluate(currentDomain => {
const anchors = Array.from(document.querySelectorAll('a')).filter(p => {
try {
return currentDomain == new URL(p.href).hostname;
@ -938,7 +938,7 @@ export class KBService implements IGBKBService {
const childLinks = [];
for (const link of filteredLinks) {
const links = await this.crawl(min, link, visited, depth + 1, maxDepth, page);
if (links){
if (links) {
childLinks.push(...links);
}
}
@ -946,9 +946,41 @@ export class KBService implements IGBKBService {
return [filename, ...childLinks]; // Include the filename of the cached file
} catch (error) {
await GBLogEx.info(min, error);
return []; // Include the filename of the cached file
}
}
async getLogoByPage(page) {
const checkPossibilities = async (page, possibilities) => {
for (const possibility of possibilities) {
const { tag, attributes } = possibility;
for (const attribute of attributes) {
const selector = `${tag}[${attribute}*="logo"]`;
const elements = await page.$$(selector);
for (const element of elements) {
const src = await page.evaluate(el => el.getAttribute('src'), element);
if (src) {
return src;
}
}
}
}
return null;
};
// Array of possibilities to check for the logo
const possibilities = [
{ tag: 'img', attributes: ['src', 'alt', 'class'] }, // Check for img elements with specific attributes
{ tag: 'svg', attributes: ['class', 'aria-label'] } // Check for svg elements with specific attributes
// Add more possibilities as needed
];
return await checkPossibilities(page, possibilities);
}
/**
* Import all .docx files in reading comprehension folder.
*/
@ -960,32 +992,50 @@ export class KBService implements IGBKBService {
): Promise<any> {
let files = [];
Fs.rmSync(min['vectorStorePath'], { recursive: true, force: true });
let path = DialogKeywords.getGBAIPath(min.botId, `gbot`);
const directoryPath = Path.join(process.env.PWD, 'work', path, 'Website');
Fs.rmSync(directoryPath, { recursive: true, force: true });
const website = min.core.getParam<string>(min.instance, 'Website', null);
if (website) {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
const response = await page.goto(website);
await page.setRequestInterception(true);
await page.screenshot({ path: 'screenshot.png' });
page.on('request', req => {
if (req.resourceType() === 'image' || req.resourceType() === 'stylesheet') {
req.abort();
} else {
req.continue();
}
});
await page.goto(website);
const logo = await this.getLogoByPage(page);
let path = DialogKeywords.getGBAIPath(min.botId);
const logoPath = Path.join(process.env.PWD, 'work', path, 'cache');
const baseUrl = page.url().split('/').slice(0, 3).join('/');
const logoBinary = await page.goto(urlJoin(baseUrl, logo));
const buffer = await logoBinary.buffer();
const logoFilename = Path.basename(logo);
Fs.writeFileSync(Path.join(logoPath, logoFilename), buffer);
await min.core['setConfig'](min, 'Logo', logoFilename);
// Extract dominant colors from the screenshot
await page.screenshot({ path: 'screenshot.png' });
const colors = await getColors('screenshot.png');
await min.core['setConfig'](min, 'Color1', colors[0].hex());
await min.core['setConfig'](min, 'Color2', colors[1].hex());
// Assuming you want the two most dominant colors
const mainColor1 = colors[0].hex();
const mainColor2 = colors[1].hex();
console.log('Main Color 1:', mainColor1);
console.log('Main Color 2:', mainColor2);
const maxDepth = 1; // Maximum depth of recursion
const maxDepth = 2; // Maximum depth of recursion
const visited = new Set<string>();
files = files.concat(await this.crawl(min, website, visited, 0, maxDepth, page));
await browser.close();
files.shift();
await CollectionUtil.asyncForEach(files, async file => {
@ -997,7 +1047,6 @@ export class KBService implements IGBKBService {
await vectorStore.addDocuments(flattenedDocuments);
await vectorStore.save(min['vectorStorePath']);
});
}
files = await walkPromise(urlJoin(localPath, 'docs'));