diff --git a/.gitignore b/.gitignore index b625b0ec..676f8ded 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ yarn-error.log package-lock.json yarn-lock.json packages/saas.gbapp.zip +logo.svg +screenshot.png diff --git a/packages/core.gbapp/services/GBCoreService.ts b/packages/core.gbapp/services/GBCoreService.ts index 1352df3c..e272274f 100644 --- a/packages/core.gbapp/services/GBCoreService.ts +++ b/packages/core.gbapp/services/GBCoreService.ts @@ -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 { + + // 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); + } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index e037a0e9..4dda229e 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -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 { diff --git a/packages/default.gbui/src/GBUIApp.js b/packages/default.gbui/src/GBUIApp.js index 65a059ad..1724e107 100644 --- a/packages/default.gbui/src/GBUIApp.js +++ b/packages/default.gbui/src/GBUIApp.js @@ -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 = ( - { - this.player = player; - }} - /> - ); - break; - case 'image': + playerComponent = ( + { + this.player = player; + }} + /> + ); + break; + case 'image': playerComponent = ( ; let gbCss =
; let seo =
; - - let sideBar = ( -
- -
- ); + let sideBar =
; if (this.state.line) { if (this.state.instanceClient) { + let color1 = this.state.instanceClient.color1; gbCss = ; seo = ; const token = this.state.instanceClient.speechToken; + + document.body.style.setProperty('background-color', this.state.instanceClient.color2, 'important'); + chat = ( { @@ -329,15 +328,25 @@ class GBUIApp extends React.Component { })} /> ); - } - } - if (!this.state.instanceClient) { - sideBar = ''; + sideBar = ( +
{ + if (node) { + node.style.setProperty('background-color', this.state.instanceClient.color1, 'important'); + } + }} + > + +
+ ); + + } } - return ( + {seo}
{gbCss} diff --git a/packages/default.gbui/src/components/SidebarMenu.js b/packages/default.gbui/src/components/SidebarMenu.js index 4f3a3201..c9a543df 100644 --- a/packages/default.gbui/src/components/SidebarMenu.js +++ b/packages/default.gbui/src/components/SidebarMenu.js @@ -49,7 +49,7 @@ class SideBarMenu extends React.Component {
General Bots Logo
diff --git a/packages/gpt.gblib/services/ChatServices.ts b/packages/gpt.gblib/services/ChatServices.ts index 2f93cc19..103b2146 100644 --- a/packages/gpt.gblib/services/ChatServices.ts +++ b/packages/gpt.gblib/services/ChatServices.ts @@ -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, diff --git a/packages/kb.gbapp/services/KBService.ts b/packages/kb.gbapp/services/KBService.ts index 58a7a5dc..c99c35fd 100644 --- a/packages/kb.gbapp/services/KBService.ts +++ b/packages/kb.gbapp/services/KBService.ts @@ -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 { @@ -853,13 +852,13 @@ export class KBService implements IGBKBService { } async saveHtmlPage(min, url: string, page: Page): Promise { + 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 { 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(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 - const colors = await getColors('screenshot.png'); - - // 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); + 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()); - const maxDepth = 1; // Maximum depth of recursion + const maxDepth = 2; // Maximum depth of recursion const visited = new Set(); 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'));