- Moikas
- Posts
- How Build A Chatbot with LangChain and React as a Google Extension - Part 1 - Advanced Functionality and State Management
How Build A Chatbot with LangChain and React as a Google Extension - Part 1 - Advanced Functionality and State Management
ChatGPT x LangChain x Google Chrome Extension | Moikas
How to Create a Browser Agent pt-2
Hey Everyone!
Welcome back to Part 2 of the Guide/Walkthrough on how to create a Browser Agent. This section will add functionality to the Browser Agent we created in Part 1. If you could complete Part 1 successfully, you should have some code ready to be modified.
Read: Notion
Gumroad: https://moikapylookout.gumroad.com/
Let’s Begin
I had to install a few more libraries to ensure we needed everything.
npm install --save axios fast-deep-equal npm-watch
Your Package JSON should look like this.
"dependencies": {
"autoprefixer": "10.4.14",
"cheerio": "^1.0.0-rc.12",
"eslint": "8.44.0",
"eslint-config-next": "13.4.7",
"fast-deep-equal": "^3.1.3",
"langchain": "^0.0.102",
"next": "13.4.7",
"npm-watch": "^0.11.0",
"postcss": "8.4.24",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-markdown": "^8.0.7",
"styled-components": "^6.0.2",
"tailwindcss": "3.3.2"
}
Development
If you are ready for development, here are the npm scripts to run the project. This will prepare and build your project files and watch for updates to rebuild so that you can develop the extension in your browser. I will be using Google Chrome.
🚨 This includes your .env, so don’t publish your extension to the public!!
public/manifest.json
This will allow Google Chrome or any other Chrome-based browser like Brave or Edge to install the extension.
To install your browser, make sure to turn on Developer Mode
An Upload the Out folder produced by the Watch or Build Scripts.
Create the Initial State for the project
src/state/agent_state.js
The initial state for the Browser Agent project was created to define the starting point for the project's state. This includes the active tab, messages, user input, and the number of tokens used. The state keeps track of the application's current state, making it easier to manage state changes throughout development.
const agent_state = {
active_tab: '',
messages: [],
user_input: '',
total_tokens_used: 0,
completion_tokens_used: 0,
prompt_tokens_used: 0,
tokens_used:0
};
export default agent_state;
Create a Reducer to handle State Changes.
src/reducer/agent_reducer.js
The reducer was created to handle state changes in the Browser Agent application. It updates the application's state based on various actions, such as changing the active tab or adding a new message. Using a reducer makes it easier to manage state changes and keep track of the current state of the application throughout the development process.
export default function agent_reducer(state, action) {
switch (action.type) {
case 'active_tab':
return {...state, active_tab: action.active_tab};
case 'new_message':
return {...state, messages: [...state.messages, action.message]};
case 'user_input':
return {...state, user_input: action.user_input};
case 'total_tokens_used':
return {...state, tokens_used: state.total_tokens_used + state.tokens_used};
case 'completion_tokens_used':
return {
...state,
completion_tokens_used: action.completion_tokens_used,
};
case 'prompt_tokens_used':
return {...state, prompt_tokens_used: action.prompt_tokens_used};
case 'tokens_used':
return {...state, tokens_used: action.tokens_used};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
Create usePersistedReducer and usePrevious hook.
Data needs to persist between refresh and when the extension is closed
Storing reducer in LocalStorage
src/hooks/usePrevious.js
The usePrevious
hook was written to keep track of the previous value of a given input. It is used in the usePersistedReducer
hook to check if the current state is equal to the previous state before updating the local storage. This is necessary to prevent unnecessary updates to the local storage and ensure that the data only persists when the state changes.
import {useRef, useEffect} from 'react';
// Given any value
// This hook will return the previous value
// Whenever the current value changes
export function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
src/hooks/usePersistedReducer.js
The usePersistedReducer
hook was created to store the reducer's state in local storage so that the data would persist between refreshes and when the extension is closed. This was achieved by updating the local storage whenever the state changed. The usePrevious
hook was also used to check if the current state was equal to the previous state before updating the local storage to prevent unnecessary updates.
import {useEffect, useReducer} from 'react';
import deepEqual from 'fast-deep-equal/es6';
import {usePrevious} from './usePrevious';
export function usePersistedReducer(reducer, initialState, storageKey) {
const [state, dispatch] = useReducer(reducer, initialState, initializer);
const prevState = usePrevious(state);
function initializer() {
if (typeof localStorage === 'undefined') return initialState;
const stringState = localStorage.getItem(storageKey);
if (stringState) {
try {
return JSON.parse(stringState);
} catch (error) {
return initialState;
}
} else {
return initialState;
}
}
useEffect(() => {
const stateEqual = deepEqual(prevState, state);
if (!stateEqual && typeof localStorage !== 'undefined') {
const stringifiedState = JSON.stringify(state);
localStorage.setItem(storageKey, stringifiedState);
}
}, [state, storageKey]);
return [state, prevState, dispatch];
}
Create an Agent Context and AgentProvider
imported persisted reducer to pass state and dispatch to child components
src/components/AgentProvider.js
The Agent Provider and Agent Context were created to manage the state of the Browser Agent application. The usePersistedReducer hook was used to store the state in local storage so that the data would persist between refreshes and when the extension is closed. The Agent Context is used to share the state and dispatch across all child components, while the Agent Provider wraps the entire application to provide access to the context.
import {createContext} from 'react';
import {usePersistedReducer} from '@/hooks/usePersistedReducer';
import agent_reducer from '@/reducer/agent_reducer';
import agent_state from '@/state/agent_state';
export const AgentContext = createContext(null);
//
export default function AgentProvider({children}) {
const [state, dispatch] = usePersistedReducer(
agent_reducer,
agent_state,
'agent_state'
);
return (
<AgentContext.Provider value={{state, dispatch}}>
{children}
</AgentContext.Provider>
);
}
Reply