@ -0,0 +1,27 @@
|
|||||||
|
You are a DocsGPT bot assistant by Arc53 that provides help with programming libraries. You give thorough answers with code examples.
|
||||||
|
Given the following extracted parts of a long document and a question, create a final answer with references ("SOURCES").
|
||||||
|
ALWAYS return a "SOURCES" part in your answer. You can also remeber things from previous questions and use them in your answer.
|
||||||
|
|
||||||
|
QUESTION: How to merge tables in pandas?
|
||||||
|
=========
|
||||||
|
Content: pandas provides various facilities for easily combining together Series or DataFrame with various kinds of set logic for the indexes and relational algebra functionality in the case of join / merge-type operations.
|
||||||
|
Source: 28-pl
|
||||||
|
Content: pandas provides a single function, merge(), as the entry point for all standard database join operations between DataFrame or named Series objects: \n\npandas.merge(left, right, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'), copy=True, indicator=False, validate=None)
|
||||||
|
Source: 30-pl
|
||||||
|
=========
|
||||||
|
FINAL ANSWER: To merge two tables in pandas, you can use the pd.merge() function. The basic syntax is: \n\npd.merge(left, right, on, how) \n\nwhere left and right are the two tables to merge, on is the column to merge on, and how is the type of merge to perform. \n\nFor example, to merge the two tables df1 and df2 on the column 'id', you can use: \n\npd.merge(df1, df2, on='id', how='inner')
|
||||||
|
SOURCES: 28-pl 30-pl
|
||||||
|
|
||||||
|
QUESTION: {{ historyquestion }}
|
||||||
|
=========
|
||||||
|
CONTENT:
|
||||||
|
SOURCE:
|
||||||
|
=========
|
||||||
|
FINAL ANSWER: {{ historyanswer }}
|
||||||
|
SOURCES:
|
||||||
|
|
||||||
|
QUESTION: {{ question }}
|
||||||
|
=========
|
||||||
|
{{ summaries }}
|
||||||
|
=========
|
||||||
|
FINAL ANSWER:
|
@ -0,0 +1,54 @@
|
|||||||
|
//TODO - Add hyperlinks to text
|
||||||
|
//TODO - Styling
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
return (
|
||||||
|
//Parent div for all content shown through App.tsx routing needs to have this styling. Might change when state management is updated.
|
||||||
|
<div className="grid min-h-screen">
|
||||||
|
<article className=" mx-auto my-auto flex w-full max-w-6xl flex-col place-items-center gap-6 rounded-lg bg-gray-100 p-6 text-jet lg:p-10 xl:p-16">
|
||||||
|
<p className="text-3xl font-semibold">About DocsGPT 🦖</p>
|
||||||
|
<p className="mt-4 text-xl font-bold">
|
||||||
|
Find the information in your documentation through AI-powered
|
||||||
|
open-source chatbot. Powered by GPT-3, Faiss and LangChain.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-lg">
|
||||||
|
If you want to add your own documentation, please follow the
|
||||||
|
instruction below:
|
||||||
|
</p>
|
||||||
|
<p className="mt-4 text-lg">
|
||||||
|
1. Navigate to{' '}
|
||||||
|
<span className="bg-gray-200 italic"> /application</span> folder
|
||||||
|
</p>
|
||||||
|
<p className="mt-4 text-lg">
|
||||||
|
2. Install dependencies from{' '}
|
||||||
|
<span className="bg-gray-200 italic">
|
||||||
|
pip install -r requirements.txt
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p className="mt-4 text-lg">
|
||||||
|
3. Prepare a <span className="bg-gray-200 italic">.env</span> file.
|
||||||
|
Copy <span className="bg-gray-200 italic">.env_sample</span> and
|
||||||
|
create <span className="bg-gray-200 italic">.env</span> with your
|
||||||
|
OpenAI API token
|
||||||
|
</p>
|
||||||
|
<p className="mt-4 text-lg">
|
||||||
|
4. Run the app with{' '}
|
||||||
|
<span className="bg-gray-200 italic">python app.py</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-lg">
|
||||||
|
Currently It uses python pandas documentation, so it will respond to
|
||||||
|
information relevant to pandas. If you want to train it on different
|
||||||
|
documentation - please follow this guide.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="mt-4 text-lg">
|
||||||
|
If you want to launch it on your own server - follow this guide.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
html,
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
@ -1,55 +1,29 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { Routes, Route } from 'react-router-dom';
|
import { Routes, Route } from 'react-router-dom';
|
||||||
import Navigation from './components/Navigation/Navigation';
|
import Navigation from './Navigation';
|
||||||
import DocsGPT from './components/DocsGPT';
|
import Conversation from './conversation/Conversation';
|
||||||
import APIKeyModal from './components/APIKeyModal';
|
import About from './About';
|
||||||
import './App.css';
|
import { useState } from 'react';
|
||||||
|
import { ActiveState } from './models/misc';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
//Currently using primitive state management. Will most likely be replaced with Redux.
|
const [navState, setNavState] = useState<ActiveState>('ACTIVE');
|
||||||
const [isMobile, setIsMobile] = useState(true);
|
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
|
||||||
const [isApiModalOpen, setIsApiModalOpen] = useState(true);
|
|
||||||
const [apiKey, setApiKey] = useState('');
|
|
||||||
|
|
||||||
const handleResize = () => {
|
|
||||||
if (window.innerWidth > 768 && isMobile) {
|
|
||||||
setIsMobile(false);
|
|
||||||
} else {
|
|
||||||
setIsMobile(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
handleResize();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', handleResize);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="min-h-full min-w-full">
|
||||||
className={`${
|
|
||||||
isMobile ? 'flex-col' : 'flex-row'
|
|
||||||
} relative flex transition-all`}
|
|
||||||
>
|
|
||||||
<APIKeyModal
|
|
||||||
apiKey={apiKey}
|
|
||||||
setApiKey={setApiKey}
|
|
||||||
isApiModalOpen={isApiModalOpen}
|
|
||||||
setIsApiModalOpen={setIsApiModalOpen}
|
|
||||||
/>
|
|
||||||
<Navigation
|
<Navigation
|
||||||
isMobile={isMobile}
|
navState={navState}
|
||||||
isMenuOpen={isMenuOpen}
|
setNavState={(val: ActiveState) => setNavState(val)}
|
||||||
setIsMenuOpen={setIsMenuOpen}
|
|
||||||
setIsApiModalOpen={setIsApiModalOpen}
|
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
className={`transition-all duration-200 ${
|
||||||
|
navState === 'ACTIVE' ? 'ml-0 md:ml-72 lg:ml-96' : ' ml-0 md:ml-16'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<DocsGPT isMenuOpen={isMenuOpen} />} />
|
<Route path="/" element={<Conversation />} />
|
||||||
|
<Route path="/about" element={<About />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
export default function Avatar({
|
||||||
|
avatar,
|
||||||
|
size,
|
||||||
|
}: {
|
||||||
|
avatar: string;
|
||||||
|
size?: 'SMALL' | 'MEDIUM' | 'LARGE';
|
||||||
|
}) {
|
||||||
|
return <div>{avatar}</div>;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
export default function Hero({ className = '' }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<div className={`flex flex-col ${className}`}>
|
||||||
|
<p className="mb-10 text-center text-4xl font-semibold">
|
||||||
|
DocsGPT <span className="text-3xl">🦖</span>
|
||||||
|
</p>
|
||||||
|
<p className="mb-3 text-center">
|
||||||
|
Welcome to DocsGPT, your technical documentation assistant!
|
||||||
|
</p>
|
||||||
|
<p className="mb-3 text-center">
|
||||||
|
Enter a query related to the information in the documentation you
|
||||||
|
selected to receive and we will provide you with the most relevant
|
||||||
|
answers.
|
||||||
|
</p>
|
||||||
|
<p className="text-center">
|
||||||
|
Start by entering your query in the input field below and we will do the
|
||||||
|
rest!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
import Arrow1 from './assets/arrow.svg';
|
||||||
|
import Hamburger from './assets/hamburger.svg';
|
||||||
|
import Key from './assets/key.svg';
|
||||||
|
import Info from './assets/info.svg';
|
||||||
|
import Link from './assets/link.svg';
|
||||||
|
import { ActiveState } from './models/misc';
|
||||||
|
import APIKeyModal from './preferences/APIKeyModal';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { selectApiKeyStatus } from './preferences/preferenceSlice';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
//TODO - Need to replace Chat button to open secondary nav with scrollable past chats option and new chat at top
|
||||||
|
//TODO - Need to add Discord and Github links
|
||||||
|
|
||||||
|
export default function Navigation({
|
||||||
|
navState,
|
||||||
|
setNavState,
|
||||||
|
}: {
|
||||||
|
navState: ActiveState;
|
||||||
|
setNavState: (val: ActiveState) => void;
|
||||||
|
}) {
|
||||||
|
const isApiKeySet = useSelector(selectApiKeyStatus);
|
||||||
|
const [apiKeyModalState, setApiKeyModalState] = useState<ActiveState>(
|
||||||
|
isApiKeySet ? 'INACTIVE' : 'ACTIVE',
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
navState === 'INACTIVE' && '-ml-96 md:-ml-60 lg:-ml-80'
|
||||||
|
} fixed z-10 flex h-full w-72 flex-col border-r-2 border-gray-100 bg-gray-50 transition-all duration-200 lg:w-96`}
|
||||||
|
>
|
||||||
|
<div className={'h-16 w-full border-b-2 border-gray-100'}>
|
||||||
|
<button
|
||||||
|
className="float-right mr-5 mt-5 h-5 w-5"
|
||||||
|
onClick={() =>
|
||||||
|
setNavState(navState === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={Arrow1}
|
||||||
|
alt="menu toggle"
|
||||||
|
className={`${
|
||||||
|
navState === 'INACTIVE' ? 'rotate-180' : 'rotate-0'
|
||||||
|
} m-auto w-3 transition-all duration-200`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex-grow border-b-2 border-gray-100"></div>
|
||||||
|
|
||||||
|
<div className="flex h-16 flex-col border-b-2 border-gray-100">
|
||||||
|
<div
|
||||||
|
className="my-auto mx-4 flex h-12 cursor-pointer gap-4 rounded-md hover:bg-gray-100"
|
||||||
|
onClick={() => {
|
||||||
|
setApiKeyModalState('ACTIVE');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src={Key} alt="key" className="ml-2 w-6" />
|
||||||
|
<p className="my-auto text-eerie-black">Reset Key</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex h-48 flex-col border-b-2 border-gray-100">
|
||||||
|
<NavLink
|
||||||
|
to="/about"
|
||||||
|
className="my-auto mx-4 flex h-12 cursor-pointer gap-4 rounded-md hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
<img src={Info} alt="info" className="ml-2 w-5" />
|
||||||
|
<p className="my-auto text-eerie-black">About</p>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<div className="my-auto mx-4 flex h-12 cursor-pointer gap-4 rounded-md hover:bg-gray-100">
|
||||||
|
<img src={Link} alt="link" className="ml-2 w-5" />
|
||||||
|
<p className="my-auto text-eerie-black">Discord</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="my-auto mx-4 flex h-12 cursor-pointer gap-4 rounded-md hover:bg-gray-100">
|
||||||
|
<img src={Link} alt="link" className="ml-2 w-5" />
|
||||||
|
<p className="my-auto text-eerie-black">Github</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="fixed mt-5 ml-6 h-6 w-6 md:hidden"
|
||||||
|
onClick={() => setNavState('ACTIVE')}
|
||||||
|
>
|
||||||
|
<img src={Hamburger} alt="menu toggle" className="w-7" />
|
||||||
|
</button>
|
||||||
|
<APIKeyModal
|
||||||
|
modalState={apiKeyModalState}
|
||||||
|
setModalState={setApiKeyModalState}
|
||||||
|
isCancellable={isApiKeySet}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
Before Width: | Height: | Size: 200 B After Width: | Height: | Size: 200 B |
@ -0,0 +1,10 @@
|
|||||||
|
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_202_7)">
|
||||||
|
<path d="M750 50L50 750M750 750L50 50" stroke="black" stroke-opacity="0.54" stroke-width="100" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_202_7">
|
||||||
|
<rect width="800" height="800" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 391 B |
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="600" height="450" viewBox="0 0 600 450" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M25 25H575M25 225H575M25 425H575" stroke="black" stroke-opacity="0.54" stroke-width="50" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 254 B |
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 273 B |
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 293 B After Width: | Height: | Size: 293 B |
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="21" height="18" viewBox="0 0 21 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.00999999 18L21 9L0.00999999 0L0 7L15 9L0 11L0.00999999 18Z" fill="black" fill-opacity="0.54"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 210 B |
@ -0,0 +1,9 @@
|
|||||||
|
<svg width="30" height="33" viewBox="0 0 30 33" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<rect width="30" height="33" fill="url(#pattern0)"/>
|
||||||
|
<defs>
|
||||||
|
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||||
|
<use xlink:href="#image0_1_917" transform="scale(0.0166667 0.0151515)"/>
|
||||||
|
</pattern>
|
||||||
|
<image id="image0_1_917" width="60" height="66" xlink:href="data:image/png;base64,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"/>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
@ -1,60 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
export default function APIKeyModal({
|
|
||||||
isApiModalOpen,
|
|
||||||
setIsApiModalOpen,
|
|
||||||
apiKey,
|
|
||||||
setApiKey,
|
|
||||||
}: {
|
|
||||||
isApiModalOpen: boolean;
|
|
||||||
setIsApiModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
apiKey: string;
|
|
||||||
setApiKey: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
}) {
|
|
||||||
const [formError, setFormError] = useState(false);
|
|
||||||
|
|
||||||
const handleResetKey = () => {
|
|
||||||
if (!apiKey) {
|
|
||||||
setFormError(true);
|
|
||||||
} else {
|
|
||||||
setFormError(false);
|
|
||||||
setIsApiModalOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
isApiModalOpen ? 'visible' : 'hidden'
|
|
||||||
} absolute z-30 h-screen w-screen bg-gray-alpha`}
|
|
||||||
>
|
|
||||||
<div className="mx-auto mt-24 flex w-128 flex-col gap-4 rounded-lg bg-white p-6 shadow-lg">
|
|
||||||
<p className="text-xl text-jet">OpenAI API Key</p>
|
|
||||||
<p className="text-lg leading-5 text-gray-500">
|
|
||||||
Before you can start using DocsGPT we need you to provide an API key
|
|
||||||
for llm. Currently, we support only OpenAI but soon many more. You can
|
|
||||||
find it here.
|
|
||||||
</p>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="h-10 w-full border-b-2 border-jet focus:outline-none"
|
|
||||||
value={apiKey}
|
|
||||||
maxLength={100}
|
|
||||||
placeholder="API Key"
|
|
||||||
onChange={(e) => setApiKey(e.target.value)}
|
|
||||||
/>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
{formError && (
|
|
||||||
<p className="text-sm text-red-500">Please enter a valid API key</p>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={handleResetKey}
|
|
||||||
className="ml-auto h-10 w-20 rounded-lg bg-violet-800 text-white transition-all hover:bg-violet-700"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
export default function DocsGPT({ isMenuOpen }: { isMenuOpen: boolean }) {
|
|
||||||
return (
|
|
||||||
<div className={`${isMenuOpen ? 'md:ml-72 lg:ml-96' : 'ml-16'}`}>
|
|
||||||
Docs GPT Chat Placeholder
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import Arrow1 from './imgs/arrow.svg';
|
|
||||||
import Key from './imgs/key.svg';
|
|
||||||
import Info from './imgs/info.svg';
|
|
||||||
import Link from './imgs/link.svg';
|
|
||||||
|
|
||||||
function MobileNavigation() {
|
|
||||||
return <div>Mobile Navigation</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function DesktopNavigation({
|
|
||||||
isMenuOpen,
|
|
||||||
setIsMenuOpen,
|
|
||||||
setIsApiModalOpen,
|
|
||||||
}: {
|
|
||||||
isMenuOpen: boolean;
|
|
||||||
setIsMenuOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
setIsApiModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
isMenuOpen ? 'w-72 lg:w-96' : 'w-16'
|
|
||||||
} fixed flex h-screen flex-col border-r-2 border-gray-100 bg-gray-50 transition-all`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
isMenuOpen ? 'w-full' : 'w-16'
|
|
||||||
} ml-auto h-16 border-b-2 border-gray-100`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="float-right mr-5 mt-5 h-5 w-5"
|
|
||||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={Arrow1}
|
|
||||||
alt="menu toggle"
|
|
||||||
className={`${
|
|
||||||
isMenuOpen ? 'rotate-0' : 'rotate-180'
|
|
||||||
} m-auto w-3 transition-all`}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isMenuOpen && (
|
|
||||||
<>
|
|
||||||
<div className="flex-grow border-b-2 border-gray-100"></div>
|
|
||||||
|
|
||||||
<div className="flex h-16 flex-col border-b-2 border-gray-100">
|
|
||||||
<div
|
|
||||||
className="my-auto mx-4 flex h-12 cursor-pointer gap-4 rounded-md hover:bg-gray-100"
|
|
||||||
onClick={() => setIsApiModalOpen(true)}
|
|
||||||
>
|
|
||||||
<img src={Key} alt="key" className="ml-2 w-6" />
|
|
||||||
<p className="my-auto text-eerie-black">Reset Key</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex h-48 flex-col border-b-2 border-gray-100">
|
|
||||||
<div className="my-auto mx-4 flex h-12 cursor-pointer gap-4 rounded-md hover:bg-gray-100">
|
|
||||||
<img src={Info} alt="info" className="ml-2 w-5" />
|
|
||||||
<p className="my-auto text-eerie-black">About</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="my-auto mx-4 flex h-12 cursor-pointer gap-4 rounded-md hover:bg-gray-100">
|
|
||||||
<img src={Link} alt="link" className="ml-2 w-5" />
|
|
||||||
<p className="my-auto text-eerie-black">Discord</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="my-auto mx-4 flex h-12 cursor-pointer gap-4 rounded-md hover:bg-gray-100">
|
|
||||||
<img src={Link} alt="link" className="ml-2 w-5" />
|
|
||||||
<p className="my-auto text-eerie-black">Github</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Navigation({
|
|
||||||
isMobile,
|
|
||||||
isMenuOpen,
|
|
||||||
setIsMenuOpen,
|
|
||||||
setIsApiModalOpen,
|
|
||||||
}: {
|
|
||||||
isMobile: boolean;
|
|
||||||
isMenuOpen: boolean;
|
|
||||||
setIsMenuOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
setIsApiModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
}) {
|
|
||||||
if (isMobile) {
|
|
||||||
return <MobileNavigation />;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<DesktopNavigation
|
|
||||||
isMenuOpen={isMenuOpen}
|
|
||||||
setIsMenuOpen={setIsMenuOpen}
|
|
||||||
setIsApiModalOpen={setIsApiModalOpen}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export default function PastChat() {}
|
|
@ -0,0 +1,73 @@
|
|||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import Hero from '../Hero';
|
||||||
|
import { AppDispatch } from '../store';
|
||||||
|
import ConversationBubble from './ConversationBubble';
|
||||||
|
import {
|
||||||
|
addMessage,
|
||||||
|
fetchAnswer,
|
||||||
|
selectConversation,
|
||||||
|
selectStatus,
|
||||||
|
} from './conversationSlice';
|
||||||
|
import Send from './../assets/send.svg';
|
||||||
|
import Spinner from './../assets/spinner.svg';
|
||||||
|
|
||||||
|
export default function Conversation() {
|
||||||
|
const messages = useSelector(selectConversation);
|
||||||
|
const status = useSelector(selectStatus);
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const endMessageRef = useRef<HTMLDivElement>(null);
|
||||||
|
const inputRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
endMessageRef?.current?.scrollIntoView({ behavior: 'smooth' }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleQuestion = (question: string) => {
|
||||||
|
dispatch(addMessage({ text: question, type: 'QUESTION' }));
|
||||||
|
dispatch(fetchAnswer({ question }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center p-6">
|
||||||
|
<div className="w-10/12 transition-all md:w-1/2">
|
||||||
|
{messages.map((message, index) => {
|
||||||
|
return (
|
||||||
|
<ConversationBubble
|
||||||
|
ref={index === messages.length - 1 ? endMessageRef : null}
|
||||||
|
className="mb-7"
|
||||||
|
key={index}
|
||||||
|
message={message.text}
|
||||||
|
type={message.type}
|
||||||
|
></ConversationBubble>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{messages.length === 0 && <Hero className="mt-24"></Hero>}
|
||||||
|
</div>
|
||||||
|
<div className="fixed bottom-2 flex w-10/12 md:w-[50%]">
|
||||||
|
<div
|
||||||
|
ref={inputRef}
|
||||||
|
contentEditable
|
||||||
|
className={`min-h-5 border-000000 overflow-x-hidden; max-h-24 w-full overflow-y-auto rounded-xl border bg-white p-2 pr-9 opacity-100 focus:border-2 focus:outline-none`}
|
||||||
|
></div>
|
||||||
|
{status === 'loading' ? (
|
||||||
|
<img
|
||||||
|
src={Spinner}
|
||||||
|
className="relative right-9 animate-spin cursor-pointer"
|
||||||
|
></img>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
onClick={() => {
|
||||||
|
if (inputRef.current?.textContent) {
|
||||||
|
handleQuestion(inputRef.current.textContent);
|
||||||
|
inputRef.current.textContent = '';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
src={Send}
|
||||||
|
className="relative right-9 cursor-pointer"
|
||||||
|
></img>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { forwardRef } from 'react';
|
||||||
|
import Avatar from '../Avatar';
|
||||||
|
import { MESSAGE_TYPE } from './conversationModels';
|
||||||
|
|
||||||
|
const ConversationBubble = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
{
|
||||||
|
message: string;
|
||||||
|
type: MESSAGE_TYPE;
|
||||||
|
className: string;
|
||||||
|
}
|
||||||
|
>(function ConversationBubble({ message, type, className }, ref) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={`flex rounded-3xl ${
|
||||||
|
type === 'QUESTION' ? '' : 'bg-gray-1000'
|
||||||
|
} py-7 px-5 ${className}`}
|
||||||
|
>
|
||||||
|
<Avatar avatar={type === 'QUESTION' ? '👤' : '🦖'}></Avatar>
|
||||||
|
<p className="ml-5">{message}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ConversationBubble;
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Answer } from './conversationModels';
|
||||||
|
|
||||||
|
export function fetchAnswerApi(
|
||||||
|
question: string,
|
||||||
|
apiKey: string,
|
||||||
|
): Promise<Answer> {
|
||||||
|
// a mock answer generator, this is going to be replaced with real http call
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
let result = '';
|
||||||
|
const characters =
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
const charactersLength = characters.length;
|
||||||
|
let counter = 0;
|
||||||
|
while (counter < 5) {
|
||||||
|
result += characters.charAt(
|
||||||
|
Math.floor(Math.random() * charactersLength),
|
||||||
|
);
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
resolve({ answer: result, query: question, result });
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
export type MESSAGE_TYPE = 'QUESTION' | 'ANSWER';
|
||||||
|
export type Status = 'idle' | 'loading' | 'failed';
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
text: string;
|
||||||
|
type: MESSAGE_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConversationState {
|
||||||
|
conversation: Message[];
|
||||||
|
status: Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Answer {
|
||||||
|
answer: string;
|
||||||
|
query: string;
|
||||||
|
result: string;
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import store from '../store';
|
||||||
|
import { fetchAnswerApi } from './conversationApi';
|
||||||
|
import { Answer, ConversationState, Message } from './conversationModels';
|
||||||
|
|
||||||
|
const initialState: ConversationState = {
|
||||||
|
conversation: [],
|
||||||
|
status: 'idle',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchAnswer = createAsyncThunk<
|
||||||
|
Answer,
|
||||||
|
{ question: string },
|
||||||
|
{ state: RootState }
|
||||||
|
>('fetchAnswer', async ({ question }, { getState }) => {
|
||||||
|
const state = getState();
|
||||||
|
const answer = await fetchAnswerApi(question, state.preference.apiKey);
|
||||||
|
return answer;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const conversationSlice = createSlice({
|
||||||
|
name: 'conversation',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
addMessage(state, action: PayloadAction<Message>) {
|
||||||
|
state.conversation.push(action.payload);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers(builder) {
|
||||||
|
builder
|
||||||
|
.addCase(fetchAnswer.pending, (state) => {
|
||||||
|
state.status = 'loading';
|
||||||
|
})
|
||||||
|
.addCase(fetchAnswer.fulfilled, (state, action) => {
|
||||||
|
state.status = 'idle';
|
||||||
|
state.conversation.push({
|
||||||
|
text: action.payload.answer,
|
||||||
|
type: 'ANSWER',
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.addCase(fetchAnswer.rejected, (state) => {
|
||||||
|
state.status = 'failed';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type RootState = ReturnType<typeof store.getState>;
|
||||||
|
|
||||||
|
export const selectConversation = (state: RootState) =>
|
||||||
|
state.conversation.conversation;
|
||||||
|
|
||||||
|
export const selectStatus = (state: RootState) => state.conversation.status;
|
||||||
|
|
||||||
|
export const { addMessage } = conversationSlice.actions;
|
||||||
|
export default conversationSlice.reducer;
|
@ -0,0 +1,5 @@
|
|||||||
|
export type ActiveState = 'ACTIVE' | 'INACTIVE';
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
avatar: string;
|
||||||
|
};
|
@ -0,0 +1,83 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { ActiveState } from '../models/misc';
|
||||||
|
import { setApiKey } from './preferenceSlice';
|
||||||
|
|
||||||
|
export default function APIKeyModal({
|
||||||
|
modalState,
|
||||||
|
setModalState,
|
||||||
|
isCancellable = true,
|
||||||
|
}: {
|
||||||
|
modalState: ActiveState;
|
||||||
|
setModalState: (val: ActiveState) => void;
|
||||||
|
isCancellable?: boolean;
|
||||||
|
}) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [key, setKey] = useState('');
|
||||||
|
const [isError, setIsError] = useState(false);
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
if (key.length <= 1) {
|
||||||
|
setIsError(true);
|
||||||
|
} else {
|
||||||
|
dispatch(setApiKey(key));
|
||||||
|
setModalState('INACTIVE');
|
||||||
|
setKey('');
|
||||||
|
setIsError(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
setKey('');
|
||||||
|
setIsError(false);
|
||||||
|
setModalState('INACTIVE');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||||
|
} absolute z-30 h-screen w-screen bg-gray-alpha`}
|
||||||
|
>
|
||||||
|
<article className="mx-auto mt-24 flex w-[90vw] max-w-lg flex-col gap-4 rounded-lg bg-white p-6 shadow-lg">
|
||||||
|
<p className="text-xl text-jet">OpenAI API Key</p>
|
||||||
|
<p className="text-lg leading-5 text-gray-500">
|
||||||
|
Before you can start using DocsGPT we need you to provide an API key
|
||||||
|
for llm. Currently, we support only OpenAI but soon many more. You can
|
||||||
|
find it here.
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="h-10 w-full border-b-2 border-jet focus:outline-none"
|
||||||
|
value={key}
|
||||||
|
maxLength={100}
|
||||||
|
placeholder="API Key"
|
||||||
|
onChange={(e) => setKey(e.target.value)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-row-reverse">
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => handleSubmit()}
|
||||||
|
className="ml-auto h-10 w-20 rounded-lg bg-violet-800 text-white transition-all hover:bg-violet-700"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
{isCancellable && (
|
||||||
|
<button
|
||||||
|
onClick={() => handleCancel()}
|
||||||
|
className="ml-5 h-10 w-20 rounded-lg border border-violet-700 bg-white text-violet-800 transition-all hover:bg-violet-700 hover:text-white"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isError && (
|
||||||
|
<p className="mr-auto text-sm text-red-500">
|
||||||
|
Please enter a valid API key
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
|
interface Preference {
|
||||||
|
apiKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: Preference = {
|
||||||
|
apiKey: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prefSlice = createSlice({
|
||||||
|
name: 'preference',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setApiKey: (state, action) => {
|
||||||
|
state.apiKey = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setApiKey } = prefSlice.actions;
|
||||||
|
export default prefSlice.reducer;
|
||||||
|
|
||||||
|
type RootState = ReturnType<typeof store.getState>;
|
||||||
|
|
||||||
|
export const selectApiKey = (state: RootState) => state.preference.apiKey;
|
||||||
|
export const selectApiKeyStatus = (state: RootState) =>
|
||||||
|
!!state.preference.apiKey;
|
@ -0,0 +1,13 @@
|
|||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
import { conversationSlice } from './conversation/conversationSlice';
|
||||||
|
import { prefSlice } from './preferences/preferenceSlice';
|
||||||
|
|
||||||
|
const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
preference: prefSlice.reducer,
|
||||||
|
conversation: conversationSlice.reducer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
export default store;
|