• 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.

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

or to participate.