- Moikas
- Posts
- How Build A Chatbot with LangChain and React as a Google Extension - Part 1 | Moikas
How Build A Chatbot with LangChain and React as a Google Extension - Part 1 | Moikas
ChatGPT x LangChain x Google Chrome Extension
Let’s Build an AI Agent for our Browser - Part 1
Hey Everyone,
I decided to start another series here for you all to follow. We will be building an AI Agent for our Web Browser to give us control over how our data is used and how we interact with the internet.
This Guide is written for beginners familiar with HTML, CSS, and JavaScript, with some experience using NextJS or ReactJS.
GitHub Repo: https://github.com/Moikapy/browser-gpt/tree/pt-1
Getting Started
⭐️ Create an Account with Open AI to Generate an API Key. Required!
Create a New Project Folder,
browser-gpt
Open up your desired Text Editor in the New Folder.
We will be using Visual Studio Code
In VS Code, open your terminal.
This is to make sure we are running commands in our project file
Type:
npx create-next-app@latest
This will init the project as a Next Project
$ npx create-next-app@latest √ What is your project named? ... . √ Would you like to use TypeScript with this project? ... No √ Would you like to use ESLint with this project? ... Yes √ Would you like to use Tailwind CSS with this project? ... Yes √ Would you like to use `src/` directory with this project? ... Yes √ Use App Router (recommended)? ... Yes √ Would you like to customize the default import alias? ... Yes √ What import alias would you like configured? ... @/*
Type
git init
This will allow us to init the project for git so we can upload our project to GitHub and make Commits at each crucial step.
Type:
npm install -S langchain cheerio axios react-markdown styled-components
This will Install LangChain to your project, allowing us to create our AI Browser Agent.
LangChain is a framework for developing applications powered by language models.
Let’s Edit The package.json
These additions will allow us to prepare and build our project easily so that it can be added to Google Chrome.
....
"watch": {
"build_win": {
"patterns": [
"src",
".",
"styles/**",
"pages/**",
"public/**"
],
"ignore": [
"out",
"node_modules",
".next"
],
"extensions": [
"js",
"json",
"lock",
"tsx",
"ts"
]
},
"build_mac": {
"patterns": [
"src",
".",
"styles/**",
"pages/**",
"public/**"
],
"ignore": [
"out",
"node_modules",
".next"
],
"extensions": [
"js",
"json",
"lock",
"tsx",
"ts"
]
}
},
"scripts": {
"dev": "next dev",
"build_win": "next build && next export && npm run prepare:win",
"build_mac": "next build && next export && npm run prepare:mac",
"prepare:win": "mv ./out/_next out/assets && sed -i 's/\\\\/_next/\\\\/assets/g' ./out/**.html",
"prepare:mac": "mv ./out/_next out/assets && sed -i '' 's/\\\\/_next/\\\\/assets/g' ./out/*.html",
"watch:mac": "npm-watch build_mac",
"watch:win": "npm-watch build_win"
},
...
Update our next.config.js
This will allow us to access the API Key we will add to our .env
as well as allow us to export our project to be used as a Browser extension or web app.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
env: {
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
},
exportPathMap: async function (
defaultPathMap,
{dev, dir, outDir, distDir, buildId}
) {
return {
'/': {page: '/'},
};
},
compiler: {
// Enables the styled-components SWC transform
styledComponents: true,
},
};
module.exports = nextConfig;
Create a .env.local
file
At the root of the project, create a .env
file and paste the API Key generated by Open AI.
Be aware that this is sensitive information, and add the .env
file to the .gitignore
file
OPENAI_API_KEY=ADD_YOUR_API_KEY_HERE
Let’s Create Our Components
In the src/
folder, create a new folder named components
to be a home for our Components
These will be the building blocks of our app, allowing us to be easily set up.
What are the Basic Components of this AI Agent? We will need a way to interface with the agent outside of our terminal; the best way to accomplish this would be to
src/components/container.js
The Container will act as a wrapper for our app.
import styled from 'styled-components';
const _Container = styled.div.attrs((props) => ({
display: props.display || 'flex',
}))`
display: ${(props) => props.display};
flex-direction: column;
height: 100%;
margin: 0;
background-color: #ddd;
padding-bottom: 0.5rem;
overflow-x: hidden;
`;
export default function Container({children, display}) {
return (
<_Container display={display}>
<style global jsx>{`
@import url('<https://fonts.googleapis.com/css2?family=Noto+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap>');
html,
body,
#__next,
main {
height: 600px;
width: 500px;
margin: 0px !important;
font-family: 'Noto Serif', serif;
overflow: hidden;
}
`}</style>
{children}
</_Container>
);
}
src/components/chat_navbar.js
The Navbar will allow us to add space for our app name and a button for options in the future.
import styled from 'styled-components';
const Div = styled.div`
display: flex;
flex-direction: row;
padding: 0.5rem 1rem;
justify-content: space-between;
font-size: 1rem;
`;
export default function Chat_Navbar() {
return (
<Div>
<span>agent-gpt</span>
<svg
xmlns='<http://www.w3.org/2000/svg>'
width='16'
height='16'
fill='currentColor'
class='bi bi-three-dots-vertical'
viewBox='0 0 16 16'>
<path d='M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z' />
</svg>
</Div>
);
}
src/components/chat_message_log.js
The Chat Message Log Component will be where both the User and AI messages will be displayed. We use the ReactMarkdown Component we installed here to format our code if we want to return code blocks or have our data formatted as an HTML Header.
import styled from 'styled-components';
import ReactMarkdown from 'react-markdown';
import {useRef} from 'react';
const Div = styled.div`
flex-grow: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 1rem;
margin: 0 3px;
display: flex;
flex-direction: column;
background-color: #eee;
height:100%;
`;
export default function Chat_Message_Log() {
const chatLogRef = useRef(null);
return (
<Div ref={chatLogRef}>
{messages.map((message, index) => (
<div className={`message ${message.sender}`}>
{message.sender == 'ai' && (
<div className='icon'>
<svg xmlns='<http://www.w3.org/2000/svg>' viewBox='0 0 100 100'>
<rect width='100' height='100' fill='#ccc' />
<text
x='50'
y='65'
font-size='50'
fill='#fff'
text-anchor='middle'>
P
</text>
</svg>
</div>
)}
<div className='chat-msg'>
<ReactMarkdown>{message.content}</ReactMarkdown>
</div>
</div>
))}
</Div>
);
}
src/components/chat_message_form.js
The Form Component handles the functionality to invoke data
import styled from 'styled-components';
import Chat_Message_Textarea from './chat_message_textarea';
const Form = styled.form`
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: space-between;
align-items: start;
padding: 1rem 1rem 0;
width: inherit;
min-height: 4.6875rem;
height: 100% !important;
max-height: 6.25rem !important;
`;
export default function Chat_Message_Form({
loading,
onSubmit,
onChange,
defaultValue,
}) {
const handleKeyDown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
onSubmit(e);
}
};
return (
<Form className='input-form' onSubmit={onSubmit}>
{!loading ? (
<Chat_Message_Textarea
value={defaultValue}
onChange={onChange}
onKeyDown={handleKeyDown}
placeholder='Type your message and press enter...'
/>
) : (
<span>Processing...</span>
)}
</Form>
);
}
src/components/chat_message_textarea.js
The Text Area will be where the user inputs their data
import styled from 'styled-components';
const TextArea = styled.textarea`
display: flex;
flex-direction: row;
flex-grow: 1;
align-items: center;
padding: 0.5rem;
resize: none;
border: 1px solid #000;
border-radius: 0.5rem;
width: 450px;
height: 4.6875rem;
&:focus {
flex-grow: 1;
padding: 0.5rem;
resize: none;
border: 1px solid #000;
border-radius: 0.5rem;
}
`;
export default function Chat_Message_Textarea({
placeholder,
onChange,
onKeyDown,
value,
}) {
return (
<TextArea
placeholder={placeholder}
onChange={onChange}
onKeyDown={onKeyDown}
value={value}
/>
);
}
src/components/chat.js
Now that we have the smaller pieces let’s put them together!
import {useState, useMemo} from 'react';
//Our components
import Container from './container';
import Chat_Navbar from './chat_navbar';
import Chat_Message_Log from './chat_message_log';
import Chat_Message_Form from './chat_message_form';
const Chat = () => {
const [tab_link, setTabLink] = useState('');
const [text_input, setTextInput] = useState('');
const [isLoaded, setIsLoaded] = useState(false);
useMemo(() => {
if (typeof chrome !== 'undefined') {
async function getCurrentTabUrl() {
if (chrome && chrome.tabs) {
const tabs = await chrome?.tabs?.query({active: true});
return await tabs[0].url;
}
}
getCurrentTabUrl().then((url) => {
setTabLink(url);
return url;
});
}
}, []);
//
return (
<Container>
<Chat_Navbar />
<Chat_Message_Log messges={[]} />
<Chat_Message_Form
defaultValue={text_input}
loading={isLoaded}
onSubmit={(e) => {}}
onChange={(e) => setTextInput(e.target.value)}
/>
</Container>
);
};
export default Chat;
Let’s Add Chat to our Page!
Create a new folder in the src/
folder named pages
if one hasn’t been created already, create a new file named index.js
and delete the app/
folder.
src/pages/index.js
Once this is done, we will import the Chat component and add a little style; feel free to change the CSS to your liking.
import Chat from '@/components/chat';
export default function Home() {
return (
<>
<style global jsx>{`
@import url('<https://fonts.googleapis.com/css2?family=Noto+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap>');
html,
body,
#__next,
main {
height: 600px;
width: 500px;
margin: 0px !important;
font-family: 'Noto Serif', serif;
overflow: hidden;
}
`}</style>
<Chat />
</>
);
}
This should be all we need to give us a nice, simple UI that looks like this.
npm run dev
I will be back with part 2 of this little guide, where we add functionality to our UI
Written By A Human using an Ai Assistant, powered using GPT-3.5 and NotionAI
Reply