add
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .env.development +3 -0
- .env.production +1 -0
- .eslintignore +17 -0
- .eslintrc.cjs +44 -0
- .husky/pre-commit +6 -0
- .prettierignore +17 -0
- Dockerfile +11 -0
- index.html +13 -0
- package-lock.json +0 -0
- package.json +61 -0
- postcss.config.cjs +6 -0
- prettier.config.cjs +7 -0
- public/favicon.ico +0 -0
- public/lock-dark.svg +7 -0
- public/lock.svg +7 -0
- public/message-programming-dark.svg +6 -0
- public/message-programming.svg +10 -0
- public/message-text-dark.svg +5 -0
- public/message-text.svg +5 -0
- public/vite.svg +1 -0
- src/About.tsx +88 -0
- src/App.tsx +44 -0
- src/Hero.tsx +169 -0
- src/Modal/index.tsx +47 -0
- src/Navigation.tsx +427 -0
- src/PageNotFound.tsx +15 -0
- src/Setting.tsx +827 -0
- src/assets/add.svg +10 -0
- src/assets/alert.svg +3 -0
- src/assets/arrow-down.svg +3 -0
- src/assets/arrow-left.svg +3 -0
- src/assets/arrow-right.svg +3 -0
- src/assets/arrow.svg +3 -0
- src/assets/checkMark2.svg +3 -0
- src/assets/checkmark.svg +1 -0
- src/assets/copy.svg +3 -0
- src/assets/cute_docsgpt3.svg +0 -0
- src/assets/discord-dark.svg +3 -0
- src/assets/discord.svg +1 -0
- src/assets/dislike.svg +4 -0
- src/assets/documentation-dark.svg +3 -0
- src/assets/documentation.svg +3 -0
- src/assets/dropdown-arrow.svg +3 -0
- src/assets/edit.svg +3 -0
- src/assets/exit.svg +10 -0
- src/assets/expand.svg +4 -0
- src/assets/github-dark.svg +3 -0
- src/assets/github.svg +5 -0
- src/assets/hamburger-dark.svg +3 -0
- src/assets/hamburger.svg +3 -0
.env.development
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
# Please put appropriate value
|
2 |
+
VITE_API_HOST=http://0.0.0.0:7091
|
3 |
+
VITE_API_STREAMING=true
|
.env.production
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
VITE_API_HOST = https://gptcloud.arc53.com
|
.eslintignore
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
node_modules/
|
2 |
+
dist/
|
3 |
+
prettier.config.cjs
|
4 |
+
.eslintrc.cjs
|
5 |
+
env.d.ts
|
6 |
+
public/
|
7 |
+
assets/
|
8 |
+
vite-env.d.ts
|
9 |
+
.prettierignore
|
10 |
+
package-lock.json
|
11 |
+
package.json
|
12 |
+
postcss.config.cjs
|
13 |
+
prettier.config.cjs
|
14 |
+
tailwind.config.cjs
|
15 |
+
tsconfig.json
|
16 |
+
tsconfig.node.json
|
17 |
+
vite.config.ts
|
.eslintrc.cjs
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
env: {
|
3 |
+
browser: true,
|
4 |
+
es2021: true,
|
5 |
+
node: true,
|
6 |
+
},
|
7 |
+
extends: [
|
8 |
+
'eslint:recommended',
|
9 |
+
'plugin:@typescript-eslint/recommended',
|
10 |
+
'plugin:react/recommended',
|
11 |
+
'plugin:prettier/recommended',
|
12 |
+
],
|
13 |
+
overrides: [],
|
14 |
+
parser: '@typescript-eslint/parser',
|
15 |
+
parserOptions: {
|
16 |
+
ecmaVersion: 'latest',
|
17 |
+
sourceType: 'module',
|
18 |
+
},
|
19 |
+
plugins: ['react', 'unused-imports'],
|
20 |
+
rules: {
|
21 |
+
'unused-imports/no-unused-imports': 'error',
|
22 |
+
'react/react-in-jsx-scope': 'off',
|
23 |
+
'prettier/prettier': [
|
24 |
+
'error',
|
25 |
+
{
|
26 |
+
endOfLine: 'auto',
|
27 |
+
},
|
28 |
+
],
|
29 |
+
},
|
30 |
+
settings: {
|
31 |
+
'import/parsers': {
|
32 |
+
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
33 |
+
},
|
34 |
+
react: {
|
35 |
+
version: 'detect',
|
36 |
+
},
|
37 |
+
'import/resolver': {
|
38 |
+
node: {
|
39 |
+
paths: ['src'],
|
40 |
+
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
41 |
+
},
|
42 |
+
},
|
43 |
+
},
|
44 |
+
};
|
.husky/pre-commit
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env sh
|
2 |
+
. "$(dirname -- "$0")/_/husky.sh"
|
3 |
+
|
4 |
+
# npm test
|
5 |
+
cd frontend
|
6 |
+
npx lint-staged
|
.prettierignore
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
node_modules/
|
2 |
+
dist/
|
3 |
+
prettier.config.cjs
|
4 |
+
.eslintrc.cjs
|
5 |
+
env.d.ts
|
6 |
+
public/
|
7 |
+
assets/
|
8 |
+
vite-env.d.ts
|
9 |
+
.prettierignore
|
10 |
+
package-lock.json
|
11 |
+
package.json
|
12 |
+
postcss.config.cjs
|
13 |
+
prettier.config.cjs
|
14 |
+
tailwind.config.cjs
|
15 |
+
tsconfig.json
|
16 |
+
tsconfig.node.json
|
17 |
+
vite.config.ts
|
Dockerfile
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM node:20.6.1-bullseye-slim
|
2 |
+
|
3 |
+
|
4 |
+
WORKDIR /app
|
5 |
+
COPY package*.json ./
|
6 |
+
RUN npm install
|
7 |
+
COPY . .
|
8 |
+
|
9 |
+
EXPOSE 5173
|
10 |
+
|
11 |
+
CMD [ "npm", "run", "dev", "--" , "--host"]
|
index.html
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
+
<title>DocsGPT 🦖</title>
|
7 |
+
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
|
8 |
+
</head>
|
9 |
+
<body>
|
10 |
+
<div id="root" class="h-screen"></div>
|
11 |
+
<script type="module" src="/src/main.tsx"></script>
|
12 |
+
</body>
|
13 |
+
</html>
|
package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
package.json
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "frontend",
|
3 |
+
"private": true,
|
4 |
+
"version": "0.0.0",
|
5 |
+
"type": "module",
|
6 |
+
"scripts": {
|
7 |
+
"dev": "vite",
|
8 |
+
"build": "tsc && vite build",
|
9 |
+
"preview": "vite preview",
|
10 |
+
"lint": "eslint ./src --ext .jsx,.js,.ts,.tsx",
|
11 |
+
"lint-fix": "eslint ./src --ext .jsx,.js,.ts,.tsx --fix",
|
12 |
+
"format": "prettier ./src --write",
|
13 |
+
"prepare": "cd .. && husky install frontend/.husky"
|
14 |
+
},
|
15 |
+
"lint-staged": {
|
16 |
+
"**/*.{js,jsx,ts,tsx}": [
|
17 |
+
"npm run lint-fix",
|
18 |
+
"npm run format"
|
19 |
+
]
|
20 |
+
},
|
21 |
+
"dependencies": {
|
22 |
+
"@reduxjs/toolkit": "^1.9.2",
|
23 |
+
"@vercel/analytics": "^0.1.10",
|
24 |
+
"react": "^18.2.0",
|
25 |
+
"react-copy-to-clipboard": "^5.1.0",
|
26 |
+
"react-dom": "^18.2.0",
|
27 |
+
"react-dropzone": "^14.2.3",
|
28 |
+
"react-markdown": "^8.0.7",
|
29 |
+
"react-redux": "^8.0.5",
|
30 |
+
"react-router-dom": "^6.8.1",
|
31 |
+
"react-syntax-highlighter": "^15.5.0",
|
32 |
+
"remark-gfm": "^3.0.0"
|
33 |
+
},
|
34 |
+
"devDependencies": {
|
35 |
+
"@types/react": "^18.0.27",
|
36 |
+
"@types/react-dom": "^18.0.10",
|
37 |
+
"@types/react-syntax-highlighter": "^15.5.6",
|
38 |
+
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
39 |
+
"@typescript-eslint/parser": "^5.51.0",
|
40 |
+
"@vitejs/plugin-react": "^4.2.1",
|
41 |
+
"autoprefixer": "^10.4.13",
|
42 |
+
"eslint": "^8.33.0",
|
43 |
+
"eslint-config-prettier": "^8.6.0",
|
44 |
+
"eslint-config-standard-with-typescript": "^34.0.0",
|
45 |
+
"eslint-plugin-import": "^2.27.5",
|
46 |
+
"eslint-plugin-n": "^15.6.1",
|
47 |
+
"eslint-plugin-prettier": "^4.2.1",
|
48 |
+
"eslint-plugin-promise": "^6.1.1",
|
49 |
+
"eslint-plugin-react": "^7.32.2",
|
50 |
+
"eslint-plugin-unused-imports": "^2.0.0",
|
51 |
+
"husky": "^8.0.0",
|
52 |
+
"lint-staged": "^13.1.1",
|
53 |
+
"postcss": "^8.4.31",
|
54 |
+
"prettier": "^2.8.4",
|
55 |
+
"prettier-plugin-tailwindcss": "^0.2.2",
|
56 |
+
"tailwindcss": "^3.2.4",
|
57 |
+
"typescript": "^4.9.5",
|
58 |
+
"vite": "^5.0.12",
|
59 |
+
"vite-plugin-svgr": "^4.2.0"
|
60 |
+
}
|
61 |
+
}
|
postcss.config.cjs
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
plugins: {
|
3 |
+
tailwindcss: {},
|
4 |
+
autoprefixer: {},
|
5 |
+
},
|
6 |
+
}
|
prettier.config.cjs
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
trailingComma: 'all',
|
3 |
+
tabWidth: 2,
|
4 |
+
semi: true,
|
5 |
+
singleQuote: true,
|
6 |
+
printWidth: 80,
|
7 |
+
}
|
public/favicon.ico
ADDED
|
public/lock-dark.svg
ADDED
|
public/lock.svg
ADDED
|
public/message-programming-dark.svg
ADDED
|
public/message-programming.svg
ADDED
|
public/message-text-dark.svg
ADDED
|
public/message-text.svg
ADDED
|
public/vite.svg
ADDED
|
src/About.tsx
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
//TODO - Add hyperlinks to text
|
2 |
+
//TODO - Styling
|
3 |
+
import DocsGPT3 from './assets/cute_docsgpt3.svg';
|
4 |
+
|
5 |
+
export default function About() {
|
6 |
+
return (
|
7 |
+
<div className="mx-5 grid min-h-screen md:mx-36">
|
8 |
+
<article className="place-items-left mx-auto my-auto flex w-full max-w-6xl flex-col gap-4 rounded-3xl bg-gray-100 dark:bg-gun-metal p-6 text-jet dark:text-bright-gray lg:p-6 xl:p-10">
|
9 |
+
<div className="flex items-center">
|
10 |
+
<p className="mr-2 text-3xl">About DocsGPT</p>
|
11 |
+
<img className="h14 mb-2" src={DocsGPT3} alt="DocsGPT" />
|
12 |
+
</div>
|
13 |
+
<p className="mt-4">
|
14 |
+
Find the information in your documentation through AI-powered
|
15 |
+
<a
|
16 |
+
className="text-blue-500"
|
17 |
+
href="https://github.com/arc53/DocsGPT"
|
18 |
+
target="_blank"
|
19 |
+
rel="noreferrer"
|
20 |
+
>
|
21 |
+
{' '}
|
22 |
+
open-source{' '}
|
23 |
+
</a>
|
24 |
+
chatbot. Powered by GPT-3, Faiss and LangChain.
|
25 |
+
</p>
|
26 |
+
|
27 |
+
<div>
|
28 |
+
<p>
|
29 |
+
If you want to add your own documentation, please follow the
|
30 |
+
instruction below:
|
31 |
+
</p>
|
32 |
+
<p className="mt-4 ml-2">
|
33 |
+
1. Navigate to{' '}
|
34 |
+
<span className="bg-gray-200 dark:bg-outer-space italic"> /application</span> folder
|
35 |
+
</p>
|
36 |
+
<p className="mt-4 ml-2">
|
37 |
+
2. Install dependencies from{' '}
|
38 |
+
<span className="bg-gray-200 dark:bg-outer-space italic">
|
39 |
+
pip install -r requirements.txt
|
40 |
+
</span>
|
41 |
+
</p>
|
42 |
+
<p className="mt-4 ml-2">
|
43 |
+
3. Prepare a <span className="bg-gray-200 dark:bg-outer-space italic">.env</span> file.
|
44 |
+
Copy <span className="bg-gray-200 dark:bg-outer-space italic">.env_sample</span> and
|
45 |
+
create <span className="bg-gray-200 dark:bg-outer-space italic">.env</span> with your
|
46 |
+
OpenAI API token
|
47 |
+
</p>
|
48 |
+
<p className="mt-4 ml-2">
|
49 |
+
4. Run the app with{' '}
|
50 |
+
<span className="bg-gray-200 dark:bg-outer-space italic">python app.py</span>
|
51 |
+
</p>
|
52 |
+
</div>
|
53 |
+
|
54 |
+
<p>
|
55 |
+
Currently It uses{' '}
|
56 |
+
<span className="text-blue-950 font-medium">DocsGPT</span>{' '}
|
57 |
+
documentation, so it will respond to information relevant to{' '}
|
58 |
+
<span className="text-blue-950 font-medium">DocsGPT</span>. If you
|
59 |
+
want to train it on different documentation - please follow
|
60 |
+
<a
|
61 |
+
className="text-blue-500"
|
62 |
+
href="https://github.com/arc53/DocsGPT/wiki/How-to-train-on-other-documentation"
|
63 |
+
target="_blank"
|
64 |
+
rel="noreferrer"
|
65 |
+
>
|
66 |
+
{' '}
|
67 |
+
this guide
|
68 |
+
</a>
|
69 |
+
.
|
70 |
+
</p>
|
71 |
+
|
72 |
+
<p className="mt-4 text-left">
|
73 |
+
If you want to launch it on your own server - follow
|
74 |
+
<a
|
75 |
+
className="text-blue-500"
|
76 |
+
href="https://github.com/arc53/DocsGPT/wiki/Hosting-the-app"
|
77 |
+
target="_blank"
|
78 |
+
rel="noreferrer"
|
79 |
+
>
|
80 |
+
{' '}
|
81 |
+
this guide
|
82 |
+
</a>
|
83 |
+
.
|
84 |
+
</p>
|
85 |
+
</article>
|
86 |
+
</div>
|
87 |
+
);
|
88 |
+
}
|
src/App.tsx
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Routes, Route } from 'react-router-dom';
|
2 |
+
import Navigation from './Navigation';
|
3 |
+
import Conversation from './conversation/Conversation';
|
4 |
+
import About from './About';
|
5 |
+
import PageNotFound from './PageNotFound';
|
6 |
+
import { inject } from '@vercel/analytics';
|
7 |
+
import { useMediaQuery } from './hooks';
|
8 |
+
import { useState,useEffect } from 'react';
|
9 |
+
import Setting from './Setting';
|
10 |
+
|
11 |
+
inject();
|
12 |
+
|
13 |
+
export default function App() {
|
14 |
+
const { isMobile } = useMediaQuery();
|
15 |
+
const [navOpen, setNavOpen] = useState(!isMobile);
|
16 |
+
const selectedTheme = localStorage.getItem('selectedTheme');
|
17 |
+
useEffect(()=>{
|
18 |
+
if (selectedTheme === 'Dark') {
|
19 |
+
document.documentElement.classList.add('dark');
|
20 |
+
document.body.classList.add('dark:bg-raisin-black');
|
21 |
+
} else {
|
22 |
+
document.documentElement.classList.remove('dark');
|
23 |
+
}
|
24 |
+
},[])
|
25 |
+
return (
|
26 |
+
<div className="min-h-full min-w-full dark:bg-raisin-black">
|
27 |
+
<Navigation navOpen={navOpen} setNavOpen={setNavOpen} />
|
28 |
+
<div
|
29 |
+
className={`transition-all duration-200 ${
|
30 |
+
!isMobile
|
31 |
+
? `ml-0 ${!navOpen ? '-mt-5 md:mx-auto lg:mx-auto' : 'md:ml-72'}`
|
32 |
+
: 'ml-0 md:ml-16'
|
33 |
+
}`}
|
34 |
+
>
|
35 |
+
<Routes>
|
36 |
+
<Route path="/" element={<Conversation />} />
|
37 |
+
<Route path="/about" element={<About />} />
|
38 |
+
<Route path="*" element={<PageNotFound />} />
|
39 |
+
<Route path="/settings" element={<Setting />} />
|
40 |
+
</Routes>
|
41 |
+
</div>
|
42 |
+
</div>
|
43 |
+
);
|
44 |
+
}
|
src/Hero.tsx
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useMediaQuery } from './hooks';
|
2 |
+
import DocsGPT3 from './assets/cute_docsgpt3.svg';
|
3 |
+
|
4 |
+
export default function Hero({ className = '' }: { className?: string }) {
|
5 |
+
// const isMobile = window.innerWidth <= 768;
|
6 |
+
const { isMobile } = useMediaQuery();
|
7 |
+
const isDarkTheme = document.documentElement.classList.contains('dark');
|
8 |
+
console.log(isDarkTheme)
|
9 |
+
return (
|
10 |
+
<div className={`mt-14 ${isMobile ? 'mb-2' : 'mb-12'}flex flex-col text-black-1000 dark:text-bright-gray`}>
|
11 |
+
<div className=" mb-2 flex items-center justify-center sm:mb-10">
|
12 |
+
<p className="mr-2 text-4xl font-semibold">DocsGPT</p>
|
13 |
+
<img className="mb-2 h-14" src={DocsGPT3} alt="DocsGPT" />
|
14 |
+
</div>
|
15 |
+
{isMobile ? (
|
16 |
+
<p className="mb-3 text-center leading-6">
|
17 |
+
Welcome to <span className="font-bold ">DocsGPT</span>, your technical
|
18 |
+
documentation assistant! Start by entering your query in the input
|
19 |
+
field below, and we'll provide you with the most relevant
|
20 |
+
answers.
|
21 |
+
</p>
|
22 |
+
) : (
|
23 |
+
<>
|
24 |
+
<p className="mb-3 text-center leading-6">
|
25 |
+
Welcome to DocsGPT, your technical documentation assistant!
|
26 |
+
</p>
|
27 |
+
<p className="mb-3 text-center leading-6">
|
28 |
+
Enter a query related to the information in the documentation you
|
29 |
+
selected to receive
|
30 |
+
<br /> and we will provide you with the most relevant answers.
|
31 |
+
</p>
|
32 |
+
<p className="mb-3 text-center leading-6">
|
33 |
+
Start by entering your query in the input field below and we will do
|
34 |
+
the rest!
|
35 |
+
</p>
|
36 |
+
</>
|
37 |
+
)}
|
38 |
+
<div
|
39 |
+
className={`sections ${isMobile ? '' : 'mt-1'
|
40 |
+
} flex flex-wrap items-center justify-center gap-2 sm:gap-1 md:gap-0`}
|
41 |
+
>
|
42 |
+
{/* first */}
|
43 |
+
<div className="h-auto md:h-60 rounded-[50px] bg-gradient-to-l from-[#6EE7B7]/70 dark:from-[#D16FF8] via-[#3B82F6] dark:via-[#48E6E0] to-[#9333EA]/50 dark:to-[#C85EF6] p-1 md:rounded-tr-none md:rounded-br-none">
|
44 |
+
<div
|
45 |
+
className={`h-full rounded-[45px] bg-white dark:bg-dark-charcoal p-${isMobile ? '3.5' : '6 py-8'
|
46 |
+
} md:rounded-tr-none md:rounded-br-none`}
|
47 |
+
>
|
48 |
+
{/* Add Mobile check here */}
|
49 |
+
{isMobile ? (
|
50 |
+
<div className="flex justify-center">
|
51 |
+
<img
|
52 |
+
src={isDarkTheme ? "/message-text-dark.svg" : "/message-text.svg"}
|
53 |
+
alt="lock"
|
54 |
+
className="h-[24px] w-[24px] "
|
55 |
+
/>
|
56 |
+
<h2 className="mb-0 pl-1 text-lg font-bold">
|
57 |
+
Chat with Your Data
|
58 |
+
</h2>
|
59 |
+
</div>
|
60 |
+
) : (
|
61 |
+
<>
|
62 |
+
<img
|
63 |
+
src={isDarkTheme ? "/message-text-dark.svg" : "/message-text.svg"}
|
64 |
+
alt="lock"
|
65 |
+
className="h-[24px] w-[24px]"
|
66 |
+
/>
|
67 |
+
<h2 className="mt-2 mb-3 text-lg font-bold">
|
68 |
+
Chat with Your Data
|
69 |
+
</h2>
|
70 |
+
</>
|
71 |
+
)}
|
72 |
+
<p
|
73 |
+
className={
|
74 |
+
isMobile
|
75 |
+
? `w-[250px] text-center text-xs text-gray-500 dark:text-bright-gray`
|
76 |
+
: `w-[250px] text-xs text-gray-500 dark:text-bright-gray`
|
77 |
+
}
|
78 |
+
>
|
79 |
+
DocsGPT will use your data to answer questions. Whether its
|
80 |
+
documentation, source code, or Microsoft files, DocsGPT allows you
|
81 |
+
to have interactive conversations and find answers based on the
|
82 |
+
provided data.
|
83 |
+
</p>
|
84 |
+
</div>
|
85 |
+
</div>
|
86 |
+
{/* second */}
|
87 |
+
<div className="h-auto md:h-60 rounded-[50px] bg-gradient-to-r from-[#6EE7B7]/70 dark:from-[#D16FF8] via-[#3B82F6] dark:via-[#48E6E0] to-[#9333EA]/50 dark:to-[#C85EF6] p-1 md:rounded-none md:py-1 md:px-0">
|
88 |
+
<div
|
89 |
+
className={`h-full rounded-[45px] bg-white dark:bg-dark-charcoal p-${isMobile ? '3.5' : '6 py-6'
|
90 |
+
} md:rounded-none`}
|
91 |
+
>
|
92 |
+
{/* Add Mobile check here */}
|
93 |
+
{isMobile ? (
|
94 |
+
<div className="flex justify-center ">
|
95 |
+
<img src={isDarkTheme ? "/lock-dark.svg" : "/lock.svg"} alt="lock" className="h-[24px] w-[24px]" />
|
96 |
+
<h2 className="mb-0 pl-1 text-lg font-bold">
|
97 |
+
Secure Data Storage
|
98 |
+
</h2>
|
99 |
+
</div>
|
100 |
+
) : (
|
101 |
+
<>
|
102 |
+
<img src={isDarkTheme ? "/lock-dark.svg" : "/lock.svg"} alt="lock" className="h-[24px] w-[24px]" />
|
103 |
+
<h2 className="mt-2 mb-3 text-lg font-bold">
|
104 |
+
Secure Data Storage
|
105 |
+
</h2>
|
106 |
+
</>
|
107 |
+
)}
|
108 |
+
<p
|
109 |
+
className={
|
110 |
+
isMobile
|
111 |
+
? `w-[250px] text-center text-xs text-gray-500 dark:text-bright-gray`
|
112 |
+
: `w-[250px] text-xs text-gray-500 dark:text-bright-gray`
|
113 |
+
}
|
114 |
+
>
|
115 |
+
The security of your data is our top priority. DocsGPT ensures the
|
116 |
+
utmost protection for your sensitive information. With secure data
|
117 |
+
storage and privacy measures in place, you can trust that your
|
118 |
+
data is kept safe and confidential.
|
119 |
+
</p>
|
120 |
+
</div>
|
121 |
+
</div>
|
122 |
+
{/* third */}
|
123 |
+
<div className="h-auto md:h-60 rounded-[50px] bg-gradient-to-l from-[#6EE7B7]/70 dark:from-[#D16FF8] via-[#3B82F6] dark:via-[#48E6E0] to-[#9333EA]/50 dark:to-[#C85EF6] p-1 md:rounded-tl-none md:rounded-bl-none ">
|
124 |
+
<div
|
125 |
+
className={`h-full firefox rounded-[45px] bg-white dark:bg-dark-charcoal p-${isMobile ? '3.5' : '6 px-6 '
|
126 |
+
} lg:rounded-tl-none lg:rounded-bl-none`}
|
127 |
+
>
|
128 |
+
{/* Add Mobile check here */}
|
129 |
+
{isMobile ? (
|
130 |
+
<div className="flex justify-center">
|
131 |
+
<img
|
132 |
+
src={isDarkTheme ? "message-programming-dark.svg" : "/message-programming.svg"}
|
133 |
+
alt="lock"
|
134 |
+
className="h-[24px] w-[24px]"
|
135 |
+
/>
|
136 |
+
<h2 className="mb-0 pl-1 text-lg font-bold">
|
137 |
+
Open Source Code
|
138 |
+
</h2>
|
139 |
+
</div>
|
140 |
+
) : (
|
141 |
+
<>
|
142 |
+
<img
|
143 |
+
src={isDarkTheme ? "/message-programming-dark.svg" : "/message-programming.svg"}
|
144 |
+
alt="lock"
|
145 |
+
className="h-[24px] w-[24px]"
|
146 |
+
/>
|
147 |
+
<h2 className="mt-2 mb-3 text-lg font-bold">
|
148 |
+
Open Source Code
|
149 |
+
</h2>
|
150 |
+
</>
|
151 |
+
)}
|
152 |
+
<p
|
153 |
+
className={
|
154 |
+
isMobile
|
155 |
+
? `w-[250px] text-center text-xs text-gray-500 dark:text-bright-gray`
|
156 |
+
: `w-[250px] text-xs text-gray-500 dark:text-bright-gray`
|
157 |
+
}
|
158 |
+
>
|
159 |
+
DocsGPT is built on open source principles, promoting transparency
|
160 |
+
and collaboration. The source code is freely available, enabling
|
161 |
+
developers to contribute, enhance, and customize the app to meet
|
162 |
+
their specific needs.
|
163 |
+
</p>
|
164 |
+
</div>
|
165 |
+
</div>
|
166 |
+
</div>
|
167 |
+
</div>
|
168 |
+
);
|
169 |
+
}
|
src/Modal/index.tsx
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from 'react';
|
2 |
+
|
3 |
+
interface ModalProps {
|
4 |
+
handleSubmit: () => void;
|
5 |
+
isCancellable: boolean;
|
6 |
+
handleCancel?: () => void;
|
7 |
+
render: () => JSX.Element;
|
8 |
+
modalState: string;
|
9 |
+
isError: boolean;
|
10 |
+
errorMessage?: string;
|
11 |
+
}
|
12 |
+
const Modal = (props: ModalProps) => {
|
13 |
+
return (
|
14 |
+
<div
|
15 |
+
className={`${
|
16 |
+
props.modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
17 |
+
} absolute z-30 h-screen w-screen bg-gray-alpha`}
|
18 |
+
>
|
19 |
+
{props.render()}
|
20 |
+
<div className=" mx-auto flex w-[90vw] max-w-lg flex-row-reverse rounded-b-lg bg-white pb-5 pr-5 shadow-lg">
|
21 |
+
<div>
|
22 |
+
<button
|
23 |
+
onClick={() => props.handleSubmit()}
|
24 |
+
className="ml-auto h-10 w-20 rounded-3xl bg-violet-800 text-white transition-all hover:bg-violet-700"
|
25 |
+
>
|
26 |
+
Save
|
27 |
+
</button>
|
28 |
+
{props.isCancellable && (
|
29 |
+
<button
|
30 |
+
onClick={() => props.handleCancel && props.handleCancel()}
|
31 |
+
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"
|
32 |
+
>
|
33 |
+
Cancel
|
34 |
+
</button>
|
35 |
+
)}
|
36 |
+
</div>
|
37 |
+
{props.isError && (
|
38 |
+
<p className="mx-auto mt-2 mr-auto text-sm text-red-500">
|
39 |
+
{props.errorMessage}
|
40 |
+
</p>
|
41 |
+
)}
|
42 |
+
</div>
|
43 |
+
</div>
|
44 |
+
);
|
45 |
+
};
|
46 |
+
|
47 |
+
export default Modal;
|
src/Navigation.tsx
ADDED
@@ -0,0 +1,427 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useEffect, useRef, useState } from 'react';
|
2 |
+
import { useDispatch, useSelector } from 'react-redux';
|
3 |
+
import { NavLink, useNavigate } from 'react-router-dom';
|
4 |
+
import DocsGPT3 from './assets/cute_docsgpt3.svg';
|
5 |
+
import Documentation from './assets/documentation.svg';
|
6 |
+
import DocumentationDark from './assets/documentation-dark.svg';
|
7 |
+
import Discord from './assets/discord.svg';
|
8 |
+
import DiscordDark from './assets/discord-dark.svg';
|
9 |
+
|
10 |
+
import Arrow2 from './assets/dropdown-arrow.svg';
|
11 |
+
import Expand from './assets/expand.svg';
|
12 |
+
import Trash from './assets/trash.svg';
|
13 |
+
import Github from './assets/github.svg';
|
14 |
+
import GithubDark from './assets/github-dark.svg';
|
15 |
+
import Hamburger from './assets/hamburger.svg';
|
16 |
+
import HamburgerDark from './assets/hamburger-dark.svg';
|
17 |
+
import Info from './assets/info.svg';
|
18 |
+
import InfoDark from './assets/info-dark.svg';
|
19 |
+
import SettingGear from './assets/settingGear.svg';
|
20 |
+
import SettingGearDark from './assets/settingGear-dark.svg';
|
21 |
+
import Add from './assets/add.svg';
|
22 |
+
import UploadIcon from './assets/upload.svg';
|
23 |
+
import { ActiveState } from './models/misc';
|
24 |
+
import APIKeyModal from './preferences/APIKeyModal';
|
25 |
+
import {
|
26 |
+
selectApiKeyStatus,
|
27 |
+
selectSelectedDocs,
|
28 |
+
selectSelectedDocsStatus,
|
29 |
+
selectSourceDocs,
|
30 |
+
setSelectedDocs,
|
31 |
+
selectConversations,
|
32 |
+
setConversations,
|
33 |
+
selectConversationId,
|
34 |
+
} from './preferences/preferenceSlice';
|
35 |
+
import {
|
36 |
+
setConversation,
|
37 |
+
updateConversationId,
|
38 |
+
} from './conversation/conversationSlice';
|
39 |
+
import { useMediaQuery, useOutsideAlerter } from './hooks';
|
40 |
+
import Upload from './upload/Upload';
|
41 |
+
import { Doc, getConversations } from './preferences/preferenceApi';
|
42 |
+
import SelectDocsModal from './preferences/SelectDocsModal';
|
43 |
+
import ConversationTile from './conversation/ConversationTile';
|
44 |
+
|
45 |
+
interface NavigationProps {
|
46 |
+
navOpen: boolean;
|
47 |
+
setNavOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
48 |
+
}
|
49 |
+
|
50 |
+
export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
51 |
+
const dispatch = useDispatch();
|
52 |
+
const docs = useSelector(selectSourceDocs);
|
53 |
+
const selectedDocs = useSelector(selectSelectedDocs);
|
54 |
+
const conversations = useSelector(selectConversations);
|
55 |
+
const conversationId = useSelector(selectConversationId);
|
56 |
+
const { isMobile } = useMediaQuery();
|
57 |
+
const isDarkTheme = document.documentElement.classList.contains('dark');
|
58 |
+
const [isDocsListOpen, setIsDocsListOpen] = useState(false);
|
59 |
+
|
60 |
+
const isApiKeySet = useSelector(selectApiKeyStatus);
|
61 |
+
const [apiKeyModalState, setApiKeyModalState] =
|
62 |
+
useState<ActiveState>('INACTIVE');
|
63 |
+
|
64 |
+
const isSelectedDocsSet = useSelector(selectSelectedDocsStatus);
|
65 |
+
const [selectedDocsModalState, setSelectedDocsModalState] =
|
66 |
+
useState<ActiveState>(isSelectedDocsSet ? 'INACTIVE' : 'ACTIVE');
|
67 |
+
|
68 |
+
const [uploadModalState, setUploadModalState] =
|
69 |
+
useState<ActiveState>('INACTIVE');
|
70 |
+
|
71 |
+
const navRef = useRef(null);
|
72 |
+
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
|
73 |
+
const embeddingsName =
|
74 |
+
import.meta.env.VITE_EMBEDDINGS_NAME ||
|
75 |
+
'huggingface_sentence-transformers/all-mpnet-base-v2';
|
76 |
+
|
77 |
+
const navigate = useNavigate();
|
78 |
+
|
79 |
+
useEffect(() => {
|
80 |
+
if (!conversations) {
|
81 |
+
fetchConversations();
|
82 |
+
}
|
83 |
+
}, [conversations, dispatch]);
|
84 |
+
|
85 |
+
async function fetchConversations() {
|
86 |
+
return await getConversations()
|
87 |
+
.then((fetchedConversations) => {
|
88 |
+
dispatch(setConversations(fetchedConversations));
|
89 |
+
})
|
90 |
+
.catch((error) => {
|
91 |
+
console.error('Failed to fetch conversations: ', error);
|
92 |
+
});
|
93 |
+
}
|
94 |
+
|
95 |
+
const handleDeleteConversation = (id: string) => {
|
96 |
+
fetch(`${apiHost}/api/delete_conversation?id=${id}`, {
|
97 |
+
method: 'POST',
|
98 |
+
})
|
99 |
+
.then(() => {
|
100 |
+
fetchConversations();
|
101 |
+
})
|
102 |
+
.catch((error) => console.error(error));
|
103 |
+
};
|
104 |
+
|
105 |
+
const handleDeleteClick = (index: number, doc: Doc) => {
|
106 |
+
const docPath = 'indexes/' + 'local' + '/' + doc.name;
|
107 |
+
|
108 |
+
fetch(`${apiHost}/api/delete_old?path=${docPath}`, {
|
109 |
+
method: 'GET',
|
110 |
+
})
|
111 |
+
.then(() => {
|
112 |
+
// remove the image element from the DOM
|
113 |
+
const imageElement = document.querySelector(
|
114 |
+
`#img-${index}`,
|
115 |
+
) as HTMLElement;
|
116 |
+
const parentElement = imageElement.parentNode as HTMLElement;
|
117 |
+
parentElement.parentNode?.removeChild(parentElement);
|
118 |
+
})
|
119 |
+
.catch((error) => console.error(error));
|
120 |
+
};
|
121 |
+
|
122 |
+
const handleConversationClick = (index: string) => {
|
123 |
+
// fetch the conversation from the server and setConversation in the store
|
124 |
+
fetch(`${apiHost}/api/get_single_conversation?id=${index}`, {
|
125 |
+
method: 'GET',
|
126 |
+
})
|
127 |
+
.then((response) => response.json())
|
128 |
+
.then((data) => {
|
129 |
+
navigate('/');
|
130 |
+
dispatch(setConversation(data));
|
131 |
+
dispatch(
|
132 |
+
updateConversationId({
|
133 |
+
query: { conversationId: index },
|
134 |
+
}),
|
135 |
+
);
|
136 |
+
});
|
137 |
+
};
|
138 |
+
|
139 |
+
async function updateConversationName(updatedConversation: {
|
140 |
+
name: string;
|
141 |
+
id: string;
|
142 |
+
}) {
|
143 |
+
await fetch(`${apiHost}/api/update_conversation_name`, {
|
144 |
+
method: 'POST',
|
145 |
+
headers: {
|
146 |
+
'Content-Type': 'application/json',
|
147 |
+
},
|
148 |
+
body: JSON.stringify(updatedConversation),
|
149 |
+
})
|
150 |
+
.then((response) => response.json())
|
151 |
+
.then((data) => {
|
152 |
+
if (data) {
|
153 |
+
navigate('/');
|
154 |
+
fetchConversations();
|
155 |
+
}
|
156 |
+
})
|
157 |
+
.catch((err) => {
|
158 |
+
console.error(err);
|
159 |
+
});
|
160 |
+
}
|
161 |
+
useOutsideAlerter(
|
162 |
+
navRef,
|
163 |
+
() => {
|
164 |
+
if (isMobile && navOpen && apiKeyModalState === 'INACTIVE') {
|
165 |
+
setNavOpen(false);
|
166 |
+
setIsDocsListOpen(false);
|
167 |
+
}
|
168 |
+
},
|
169 |
+
[navOpen, isDocsListOpen, apiKeyModalState],
|
170 |
+
);
|
171 |
+
|
172 |
+
/*
|
173 |
+
Needed to fix bug where if mobile nav was closed and then window was resized to desktop, nav would still be closed but the button to open would be gone, as per #1 on issue #146
|
174 |
+
*/
|
175 |
+
|
176 |
+
useEffect(() => {
|
177 |
+
setNavOpen(!isMobile);
|
178 |
+
}, [isMobile]);
|
179 |
+
|
180 |
+
return (
|
181 |
+
<>
|
182 |
+
{!navOpen && (
|
183 |
+
<button
|
184 |
+
className="duration-25 absolute top-3 left-3 z-20 hidden transition-all md:block"
|
185 |
+
onClick={() => {
|
186 |
+
setNavOpen(!navOpen);
|
187 |
+
}}
|
188 |
+
>
|
189 |
+
<img
|
190 |
+
src={Expand}
|
191 |
+
alt="menu toggle"
|
192 |
+
className={`${!navOpen ? 'rotate-180' : 'rotate-0'
|
193 |
+
} m-auto transition-all duration-200`}
|
194 |
+
/>
|
195 |
+
</button>
|
196 |
+
)}
|
197 |
+
<div
|
198 |
+
ref={navRef}
|
199 |
+
className={`${!navOpen && '-ml-96 md:-ml-[18rem]'
|
200 |
+
} duration-20 fixed top-0 z-20 flex h-full w-72 flex-col border-r-[1px] border-b-0 dark:border-r-purple-taupe bg-white dark:bg-chinese-black transition-all dark:text-white`}
|
201 |
+
>
|
202 |
+
<div
|
203 |
+
className={'visible mt-2 flex h-[6vh] w-full justify-between md:h-12'}
|
204 |
+
>
|
205 |
+
<div className="my-auto mx-4 flex cursor-pointer gap-1.5">
|
206 |
+
<img className="mb-2 h-10" src={DocsGPT3} alt="" />
|
207 |
+
<p className="my-auto text-2xl font-semibold">DocsGPT</p>
|
208 |
+
</div>
|
209 |
+
<button
|
210 |
+
className="float-right mr-5"
|
211 |
+
onClick={() => {
|
212 |
+
setNavOpen(!navOpen);
|
213 |
+
}}
|
214 |
+
>
|
215 |
+
<img
|
216 |
+
src={Expand}
|
217 |
+
alt="menu toggle"
|
218 |
+
className={`${!navOpen ? 'rotate-180' : 'rotate-0'
|
219 |
+
} m-auto transition-all duration-200`}
|
220 |
+
/>
|
221 |
+
</button>
|
222 |
+
</div>
|
223 |
+
<NavLink
|
224 |
+
to={'/'}
|
225 |
+
onClick={() => {
|
226 |
+
dispatch(setConversation([]));
|
227 |
+
dispatch(
|
228 |
+
updateConversationId({
|
229 |
+
query: { conversationId: null },
|
230 |
+
}),
|
231 |
+
);
|
232 |
+
}}
|
233 |
+
className={({ isActive }) =>
|
234 |
+
`${isActive ? 'bg-gray-3000 dark:bg-transparent' : ''
|
235 |
+
} group sticky mx-4 mt-4 flex cursor-pointer gap-2.5 rounded-3xl border border-silver p-3 hover:border-rainy-gray dark:border-purple-taupe dark:text-white dark:hover:bg-transparent hover:bg-gray-3000`
|
236 |
+
}
|
237 |
+
>
|
238 |
+
<img
|
239 |
+
src={Add}
|
240 |
+
alt="new"
|
241 |
+
className="opacity-80 group-hover:opacity-100"
|
242 |
+
/>
|
243 |
+
<p className=" text-sm text-dove-gray group-hover:text-neutral-600 dark:text-chinese-silver dark:group-hover:text-bright-gray">
|
244 |
+
New Chat
|
245 |
+
</p>
|
246 |
+
</NavLink>
|
247 |
+
<div className="mb-auto h-[56vh] overflow-x-hidden dark:text-white overflow-y-scroll">
|
248 |
+
{conversations && (
|
249 |
+
<div>
|
250 |
+
<p className="ml-6 mt-3 text-sm font-semibold">Chats</p>
|
251 |
+
<div className="conversations-container">
|
252 |
+
{conversations?.map((conversation) => (
|
253 |
+
<ConversationTile
|
254 |
+
key={conversation.id}
|
255 |
+
conversation={conversation}
|
256 |
+
selectConversation={(id) => handleConversationClick(id)}
|
257 |
+
onDeleteConversation={(id) => handleDeleteConversation(id)}
|
258 |
+
onSave={(conversation) =>
|
259 |
+
updateConversationName(conversation)
|
260 |
+
}
|
261 |
+
/>
|
262 |
+
))}
|
263 |
+
</div>
|
264 |
+
</div>
|
265 |
+
)}
|
266 |
+
</div>
|
267 |
+
|
268 |
+
<div className="flex h-auto flex-col justify-end text-eerie-black dark:text-white">
|
269 |
+
<div className="flex flex-col-reverse border-b-[1px] dark:border-b-purple-taupe">
|
270 |
+
<div className="relative my-4 flex gap-2 px-2">
|
271 |
+
<div
|
272 |
+
className="flex h-12 w-5/6 cursor-pointer justify-between rounded-3xl border-2 dark:border-chinese-silver bg-white dark:bg-chinese-black"
|
273 |
+
onClick={() => setIsDocsListOpen(!isDocsListOpen)}
|
274 |
+
>
|
275 |
+
{selectedDocs && (
|
276 |
+
<p className="my-3 mx-4 overflow-hidden text-ellipsis whitespace-nowrap">
|
277 |
+
{selectedDocs.name} {selectedDocs.version}
|
278 |
+
</p>
|
279 |
+
)}
|
280 |
+
<img
|
281 |
+
src={Arrow2}
|
282 |
+
alt="arrow"
|
283 |
+
className={`${!isDocsListOpen ? 'rotate-0' : 'rotate-180'
|
284 |
+
} ml-auto mr-3 w-3 transition-all`}
|
285 |
+
/>
|
286 |
+
</div>
|
287 |
+
<img
|
288 |
+
className="mt-2 h-9 w-9 hover:cursor-pointer"
|
289 |
+
src={UploadIcon}
|
290 |
+
onClick={() => setUploadModalState('ACTIVE')}
|
291 |
+
></img>
|
292 |
+
{isDocsListOpen && (
|
293 |
+
<div className="absolute top-12 left-0 right-6 z-10 ml-2 mr-4 max-h-52 overflow-y-scroll bg-white dark:bg-chinese-black shadow-lg">
|
294 |
+
{docs ? (
|
295 |
+
docs.map((doc, index) => {
|
296 |
+
if (doc.model === embeddingsName) {
|
297 |
+
return (
|
298 |
+
<div
|
299 |
+
key={index}
|
300 |
+
onClick={() => {
|
301 |
+
dispatch(setSelectedDocs(doc));
|
302 |
+
setIsDocsListOpen(false);
|
303 |
+
}}
|
304 |
+
className="flex h-10 w-full cursor-pointer items-center justify-between border-x-2 border-b-[1px] dark:border-purple-taupe hover:bg-gray-100 dark:hover:bg-purple-taupe"
|
305 |
+
>
|
306 |
+
<p className="ml-5 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3">
|
307 |
+
{doc.name} {doc.version}
|
308 |
+
</p>
|
309 |
+
{doc.location === 'local' && (
|
310 |
+
<img
|
311 |
+
src={Trash}
|
312 |
+
alt="Delete"
|
313 |
+
className="mr-4 h-4 w-4 cursor-pointer hover:opacity-50"
|
314 |
+
id={`img-${index}`}
|
315 |
+
onClick={(event) => {
|
316 |
+
event.stopPropagation();
|
317 |
+
handleDeleteClick(index, doc);
|
318 |
+
}}
|
319 |
+
/>
|
320 |
+
)}
|
321 |
+
</div>
|
322 |
+
);
|
323 |
+
}
|
324 |
+
})
|
325 |
+
) : (
|
326 |
+
<div className="h-10 w-full cursor-pointer border-b-[1px] dark:border-b-purple-taupe hover:bg-gray-100">
|
327 |
+
<p className="ml-5 py-3">No default documentation.</p>
|
328 |
+
</div>
|
329 |
+
)}
|
330 |
+
</div>
|
331 |
+
)}
|
332 |
+
</div>
|
333 |
+
<p className="ml-6 mt-3 text-sm font-semibold">Source Docs</p>
|
334 |
+
</div>
|
335 |
+
<div className="flex flex-col gap-2 border-b-[1px] dark:border-b-purple-taupe py-2">
|
336 |
+
<NavLink
|
337 |
+
to="/settings"
|
338 |
+
className={({ isActive }) =>
|
339 |
+
`my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-purple-taupe ${isActive ? 'bg-gray-3000 dark:bg-transparent' : ''
|
340 |
+
}`
|
341 |
+
}
|
342 |
+
>
|
343 |
+
<img
|
344 |
+
src={isDarkTheme ? SettingGearDark : SettingGear}
|
345 |
+
alt="settings"
|
346 |
+
className="ml-2 w-5 opacity-60"
|
347 |
+
/>
|
348 |
+
<p className="my-auto text-sm text-eerie-black dark:text-white">Settings</p>
|
349 |
+
</NavLink>
|
350 |
+
</div>
|
351 |
+
|
352 |
+
<div className="flex flex-col gap-2 border-b-[1.5px] dark:border-b-purple-taupe py-2">
|
353 |
+
<NavLink
|
354 |
+
to="/about"
|
355 |
+
className={({ isActive }) =>
|
356 |
+
`my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-purple-taupe ${isActive ? 'bg-gray-3000 dark:bg-purple-taupe' : ''
|
357 |
+
}`
|
358 |
+
}
|
359 |
+
>
|
360 |
+
<img src={isDarkTheme ? InfoDark : Info} alt="info" className="ml-2 w-5" />
|
361 |
+
<p className="my-auto text-sm">About</p>
|
362 |
+
</NavLink>
|
363 |
+
|
364 |
+
<a
|
365 |
+
href="https://docs.docsgpt.co.uk/"
|
366 |
+
target="_blank"
|
367 |
+
rel="noreferrer"
|
368 |
+
className="my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-purple-taupe"
|
369 |
+
>
|
370 |
+
<img
|
371 |
+
src={isDarkTheme ? DocumentationDark : Documentation}
|
372 |
+
alt="documentation"
|
373 |
+
className="ml-2 w-5"
|
374 |
+
/>
|
375 |
+
<p className="my-auto text-sm ">Documentation</p>
|
376 |
+
</a>
|
377 |
+
<a
|
378 |
+
href="https://discord.gg/WHJdfbQDR4"
|
379 |
+
target="_blank"
|
380 |
+
rel="noreferrer"
|
381 |
+
className="my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-purple-taupe"
|
382 |
+
>
|
383 |
+
<img src={isDarkTheme ? DiscordDark : Discord} alt="discord-link" className="ml-2 w-5" />
|
384 |
+
<p className="my-auto text-sm">
|
385 |
+
Visit our Discord
|
386 |
+
</p>
|
387 |
+
</a>
|
388 |
+
|
389 |
+
<a
|
390 |
+
href="https://github.com/arc53/DocsGPT"
|
391 |
+
target="_blank"
|
392 |
+
rel="noreferrer"
|
393 |
+
className="mt-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-purple-taupe"
|
394 |
+
>
|
395 |
+
<img src={isDarkTheme ? GithubDark : Github} alt="github-link" className="ml-2 w-5" />
|
396 |
+
<p className="my-auto text-sm">
|
397 |
+
Visit our Github
|
398 |
+
</p>
|
399 |
+
</a>
|
400 |
+
</div>
|
401 |
+
</div>
|
402 |
+
</div>
|
403 |
+
<div className="fixed z-10 h-16 w-full border-b-2 dark:border-b-purple-taupe bg-gray-50 dark:bg-chinese-black md:hidden">
|
404 |
+
<button
|
405 |
+
className="mt-5 ml-6 h-6 w-6 md:hidden"
|
406 |
+
onClick={() => setNavOpen(true)}
|
407 |
+
>
|
408 |
+
<img src={isDarkTheme ? HamburgerDark :Hamburger} alt="menu toggle" className="w-7" />
|
409 |
+
</button>
|
410 |
+
</div>
|
411 |
+
<SelectDocsModal
|
412 |
+
modalState={selectedDocsModalState}
|
413 |
+
setModalState={setSelectedDocsModalState}
|
414 |
+
isCancellable={isSelectedDocsSet}
|
415 |
+
/>
|
416 |
+
<APIKeyModal
|
417 |
+
modalState={apiKeyModalState}
|
418 |
+
setModalState={setApiKeyModalState}
|
419 |
+
isCancellable={isApiKeySet}
|
420 |
+
/>
|
421 |
+
<Upload
|
422 |
+
modalState={uploadModalState}
|
423 |
+
setModalState={setUploadModalState}
|
424 |
+
></Upload>
|
425 |
+
</>
|
426 |
+
);
|
427 |
+
}
|
src/PageNotFound.tsx
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Link } from 'react-router-dom';
|
2 |
+
|
3 |
+
export default function PageNotFound() {
|
4 |
+
return (
|
5 |
+
<div className="mx-5 grid min-h-screen md:mx-36">
|
6 |
+
<p className="mx-auto my-auto mt-20 flex w-full max-w-6xl flex-col place-items-center gap-6 rounded-3xl bg-gray-100 p-6 text-jet lg:p-10 xl:p-16">
|
7 |
+
<h1>404</h1>
|
8 |
+
<p>The page you are looking for does not exist.</p>
|
9 |
+
<button className="pointer-cursor mr-4 flex cursor-pointer items-center justify-center rounded-full bg-blue-1000 py-2 px-4 text-white hover:bg-blue-3000">
|
10 |
+
<Link to="/">Go Back Home</Link>
|
11 |
+
</button>
|
12 |
+
</p>
|
13 |
+
</div>
|
14 |
+
);
|
15 |
+
}
|
src/Setting.tsx
ADDED
@@ -0,0 +1,827 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState, useEffect } from 'react';
|
2 |
+
import { useSelector, useDispatch } from 'react-redux';
|
3 |
+
import Arrow2 from './assets/dropdown-arrow.svg';
|
4 |
+
import ArrowLeft from './assets/arrow-left.svg';
|
5 |
+
import ArrowRight from './assets/arrow-right.svg';
|
6 |
+
import Trash from './assets/trash.svg';
|
7 |
+
import {
|
8 |
+
selectPrompt,
|
9 |
+
setPrompt,
|
10 |
+
selectSourceDocs,
|
11 |
+
} from './preferences/preferenceSlice';
|
12 |
+
import { Doc } from './preferences/preferenceApi';
|
13 |
+
type PromptProps = {
|
14 |
+
prompts: { name: string; id: string; type: string }[];
|
15 |
+
selectedPrompt: { name: string; id: string; type: string };
|
16 |
+
onSelectPrompt: (name: string, id: string, type: string) => void;
|
17 |
+
setPrompts: (prompts: { name: string; id: string; type: string }[]) => void;
|
18 |
+
apiHost: string;
|
19 |
+
};
|
20 |
+
|
21 |
+
const Setting: React.FC = () => {
|
22 |
+
const tabs = ['General', 'Prompts', 'Documents'];
|
23 |
+
//const tabs = ['General', 'Prompts', 'Documents', 'Widgets'];
|
24 |
+
|
25 |
+
const [activeTab, setActiveTab] = useState('General');
|
26 |
+
const [prompts, setPrompts] = useState<
|
27 |
+
{ name: string; id: string; type: string }[]
|
28 |
+
>([]);
|
29 |
+
const selectedPrompt = useSelector(selectPrompt);
|
30 |
+
const [isAddPromptModalOpen, setAddPromptModalOpen] = useState(false);
|
31 |
+
const documents = useSelector(selectSourceDocs);
|
32 |
+
const [isAddDocumentModalOpen, setAddDocumentModalOpen] = useState(false);
|
33 |
+
|
34 |
+
const dispatch = useDispatch();
|
35 |
+
|
36 |
+
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
|
37 |
+
const [widgetScreenshot, setWidgetScreenshot] = useState<File | null>(null);
|
38 |
+
|
39 |
+
const updateWidgetScreenshot = (screenshot: File | null) => {
|
40 |
+
setWidgetScreenshot(screenshot);
|
41 |
+
};
|
42 |
+
|
43 |
+
useEffect(() => {
|
44 |
+
const fetchPrompts = async () => {
|
45 |
+
try {
|
46 |
+
const response = await fetch(`${apiHost}/api/get_prompts`);
|
47 |
+
if (!response.ok) {
|
48 |
+
throw new Error('Failed to fetch prompts');
|
49 |
+
}
|
50 |
+
const promptsData = await response.json();
|
51 |
+
setPrompts(promptsData);
|
52 |
+
} catch (error) {
|
53 |
+
console.error(error);
|
54 |
+
}
|
55 |
+
};
|
56 |
+
|
57 |
+
fetchPrompts();
|
58 |
+
}, []);
|
59 |
+
|
60 |
+
const onDeletePrompt = (name: string, id: string) => {
|
61 |
+
setPrompts(prompts.filter((prompt) => prompt.id !== id));
|
62 |
+
|
63 |
+
fetch(`${apiHost}/api/delete_prompt`, {
|
64 |
+
method: 'POST',
|
65 |
+
headers: {
|
66 |
+
'Content-Type': 'application/json',
|
67 |
+
},
|
68 |
+
// send id in body only
|
69 |
+
body: JSON.stringify({ id: id }),
|
70 |
+
})
|
71 |
+
.then((response) => {
|
72 |
+
if (!response.ok) {
|
73 |
+
throw new Error('Failed to delete prompt');
|
74 |
+
}
|
75 |
+
})
|
76 |
+
.catch((error) => {
|
77 |
+
console.error(error);
|
78 |
+
});
|
79 |
+
};
|
80 |
+
|
81 |
+
const handleDeleteClick = (index: number, doc: Doc) => {
|
82 |
+
const docPath = 'indexes/' + 'local' + '/' + doc.name;
|
83 |
+
|
84 |
+
fetch(`${apiHost}/api/delete_old?path=${docPath}`, {
|
85 |
+
method: 'GET',
|
86 |
+
})
|
87 |
+
.then(() => {
|
88 |
+
// remove the image element from the DOM
|
89 |
+
const imageElement = document.querySelector(
|
90 |
+
`#img-${index}`,
|
91 |
+
) as HTMLElement;
|
92 |
+
const parentElement = imageElement.parentNode as HTMLElement;
|
93 |
+
parentElement.parentNode?.removeChild(parentElement);
|
94 |
+
})
|
95 |
+
.catch((error) => console.error(error));
|
96 |
+
};
|
97 |
+
|
98 |
+
return (
|
99 |
+
<div className="p-4 pt-20 md:p-12 wa">
|
100 |
+
<p className="text-2xl font-bold text-eerie-black dark:text-bright-gray">Settings</p>
|
101 |
+
<div className="mt-6 flex flex-row items-center space-x-4 overflow-x-auto md:space-x-8 ">
|
102 |
+
<div className="md:hidden">
|
103 |
+
<button
|
104 |
+
onClick={() => scrollTabs(-1)}
|
105 |
+
className="flex h-8 w-8 items-center justify-center rounded-full border-2 border-purple-30 transition-all hover:bg-gray-100"
|
106 |
+
>
|
107 |
+
<img src={ArrowLeft} alt="left-arrow" className="h-6 w-6" />
|
108 |
+
</button>
|
109 |
+
</div>
|
110 |
+
<div className="flex flex-nowrap space-x-4 overflow-x-auto md:space-x-8">
|
111 |
+
{tabs.map((tab, index) => (
|
112 |
+
<button
|
113 |
+
key={index}
|
114 |
+
onClick={() => setActiveTab(tab)}
|
115 |
+
className={`h-9 rounded-3xl px-4 font-bold ${activeTab === tab
|
116 |
+
? 'bg-purple-3000 text-purple-30 dark:bg-dark-charcoal'
|
117 |
+
: 'text-gray-6000'
|
118 |
+
}`}
|
119 |
+
>
|
120 |
+
{tab}
|
121 |
+
</button>
|
122 |
+
))}
|
123 |
+
</div>
|
124 |
+
<div className="md:hidden">
|
125 |
+
<button
|
126 |
+
onClick={() => scrollTabs(1)}
|
127 |
+
className="flex h-8 w-8 items-center justify-center rounded-full border-2 border-purple-30 hover:bg-gray-100"
|
128 |
+
>
|
129 |
+
<img src={ArrowRight} alt="right-arrow" className="h-6 w-6" />
|
130 |
+
</button>
|
131 |
+
</div>
|
132 |
+
</div>
|
133 |
+
{renderActiveTab()}
|
134 |
+
|
135 |
+
{/* {activeTab === 'Widgets' && (
|
136 |
+
<Widgets
|
137 |
+
widgetScreenshot={widgetScreenshot}
|
138 |
+
onWidgetScreenshotChange={updateWidgetScreenshot}
|
139 |
+
/>
|
140 |
+
)} */}
|
141 |
+
</div>
|
142 |
+
);
|
143 |
+
|
144 |
+
function scrollTabs(direction: number) {
|
145 |
+
const container = document.querySelector('.flex-nowrap');
|
146 |
+
if (container) {
|
147 |
+
container.scrollLeft += direction * 100; // Adjust the scroll amount as needed
|
148 |
+
}
|
149 |
+
}
|
150 |
+
|
151 |
+
function renderActiveTab() {
|
152 |
+
switch (activeTab) {
|
153 |
+
case 'General':
|
154 |
+
return <General />;
|
155 |
+
case 'Prompts':
|
156 |
+
return (
|
157 |
+
<Prompts
|
158 |
+
prompts={prompts}
|
159 |
+
selectedPrompt={selectedPrompt}
|
160 |
+
onSelectPrompt={(name, id, type) =>
|
161 |
+
dispatch(setPrompt({ name: name, id: id, type: type }))
|
162 |
+
}
|
163 |
+
setPrompts={setPrompts}
|
164 |
+
apiHost={apiHost}
|
165 |
+
/>
|
166 |
+
);
|
167 |
+
case 'Documents':
|
168 |
+
return (
|
169 |
+
<Documents
|
170 |
+
documents={documents}
|
171 |
+
handleDeleteDocument={handleDeleteClick}
|
172 |
+
/>
|
173 |
+
);
|
174 |
+
case 'Widgets':
|
175 |
+
return (
|
176 |
+
<Widgets
|
177 |
+
widgetScreenshot={widgetScreenshot} // Add this line
|
178 |
+
onWidgetScreenshotChange={updateWidgetScreenshot} // Add this line
|
179 |
+
/>
|
180 |
+
);
|
181 |
+
default:
|
182 |
+
return null;
|
183 |
+
}
|
184 |
+
}
|
185 |
+
};
|
186 |
+
|
187 |
+
const General: React.FC = () => {
|
188 |
+
const themes = ['Light', 'Dark'];
|
189 |
+
const languages = ['English'];
|
190 |
+
const [selectedTheme, setSelectedTheme] = useState(localStorage.getItem('selectedTheme') || themes[0]);
|
191 |
+
const [selectedLanguage, setSelectedLanguage] = useState(languages[0]);
|
192 |
+
|
193 |
+
useEffect(() => {
|
194 |
+
if (selectedTheme === 'Dark') {
|
195 |
+
document.documentElement.classList.add('dark');
|
196 |
+
document.documentElement.classList.add('dark:bg-raisin-black');
|
197 |
+
} else {
|
198 |
+
document.documentElement.classList.remove('dark');
|
199 |
+
}
|
200 |
+
localStorage.setItem('selectedTheme', selectedTheme);
|
201 |
+
}, [selectedTheme]);
|
202 |
+
|
203 |
+
return (
|
204 |
+
<div className="mt-[59px]">
|
205 |
+
<div className="mb-4">
|
206 |
+
<p className="font-bold text-jet dark:text-bright-gray">Select Theme</p>
|
207 |
+
<Dropdown
|
208 |
+
options={themes}
|
209 |
+
selectedValue={selectedTheme}
|
210 |
+
onSelect={setSelectedTheme}
|
211 |
+
/>
|
212 |
+
</div>
|
213 |
+
<div>
|
214 |
+
<p className="font-bold text-jet dark:text-bright-gray">Select Language</p>
|
215 |
+
<Dropdown
|
216 |
+
options={languages}
|
217 |
+
selectedValue={selectedLanguage}
|
218 |
+
onSelect={setSelectedLanguage}
|
219 |
+
/>
|
220 |
+
</div>
|
221 |
+
</div>
|
222 |
+
);
|
223 |
+
};
|
224 |
+
|
225 |
+
export default Setting;
|
226 |
+
|
227 |
+
const Prompts: React.FC<PromptProps> = ({
|
228 |
+
prompts,
|
229 |
+
selectedPrompt,
|
230 |
+
onSelectPrompt,
|
231 |
+
setPrompts,
|
232 |
+
apiHost,
|
233 |
+
}) => {
|
234 |
+
const handleSelectPrompt = ({
|
235 |
+
name,
|
236 |
+
id,
|
237 |
+
type,
|
238 |
+
}: {
|
239 |
+
name: string;
|
240 |
+
id: string;
|
241 |
+
type: string;
|
242 |
+
}) => {
|
243 |
+
setNewPromptName(name);
|
244 |
+
onSelectPrompt(name, id, type);
|
245 |
+
};
|
246 |
+
const [newPromptName, setNewPromptName] = useState(selectedPrompt.name);
|
247 |
+
const [newPromptContent, setNewPromptContent] = useState('');
|
248 |
+
|
249 |
+
const handleAddPrompt = async () => {
|
250 |
+
try {
|
251 |
+
const response = await fetch(`${apiHost}/api/create_prompt`, {
|
252 |
+
method: 'POST',
|
253 |
+
headers: {
|
254 |
+
'Content-Type': 'application/json',
|
255 |
+
},
|
256 |
+
body: JSON.stringify({
|
257 |
+
name: newPromptName,
|
258 |
+
content: newPromptContent,
|
259 |
+
}),
|
260 |
+
});
|
261 |
+
if (!response.ok) {
|
262 |
+
throw new Error('Failed to add prompt');
|
263 |
+
}
|
264 |
+
const newPrompt = await response.json();
|
265 |
+
if (setPrompts) {
|
266 |
+
setPrompts([
|
267 |
+
...prompts,
|
268 |
+
{ name: newPromptName, id: newPrompt.id, type: 'private' },
|
269 |
+
]);
|
270 |
+
}
|
271 |
+
onSelectPrompt(newPromptName, newPrompt.id, newPromptContent);
|
272 |
+
setNewPromptName(newPromptName);
|
273 |
+
} catch (error) {
|
274 |
+
console.error(error);
|
275 |
+
}
|
276 |
+
};
|
277 |
+
|
278 |
+
const handleDeletePrompt = () => {
|
279 |
+
setPrompts(prompts.filter((prompt) => prompt.id !== selectedPrompt.id));
|
280 |
+
console.log('selectedPrompt.id', selectedPrompt.id);
|
281 |
+
|
282 |
+
fetch(`${apiHost}/api/delete_prompt`, {
|
283 |
+
method: 'POST',
|
284 |
+
headers: {
|
285 |
+
'Content-Type': 'application/json',
|
286 |
+
},
|
287 |
+
body: JSON.stringify({ id: selectedPrompt.id }),
|
288 |
+
})
|
289 |
+
.then((response) => {
|
290 |
+
if (!response.ok) {
|
291 |
+
throw new Error('Failed to delete prompt');
|
292 |
+
}
|
293 |
+
// get 1st prompt and set it as selected
|
294 |
+
if (prompts.length > 0) {
|
295 |
+
onSelectPrompt(prompts[0].name, prompts[0].id, prompts[0].type);
|
296 |
+
setNewPromptName(prompts[0].name);
|
297 |
+
}
|
298 |
+
})
|
299 |
+
.catch((error) => {
|
300 |
+
console.error(error);
|
301 |
+
});
|
302 |
+
};
|
303 |
+
|
304 |
+
useEffect(() => {
|
305 |
+
const fetchPromptContent = async () => {
|
306 |
+
console.log('fetching prompt content');
|
307 |
+
try {
|
308 |
+
const response = await fetch(
|
309 |
+
`${apiHost}/api/get_single_prompt?id=${selectedPrompt.id}`,
|
310 |
+
{
|
311 |
+
method: 'GET',
|
312 |
+
headers: {
|
313 |
+
'Content-Type': 'application/json',
|
314 |
+
},
|
315 |
+
},
|
316 |
+
);
|
317 |
+
if (!response.ok) {
|
318 |
+
throw new Error('Failed to fetch prompt content');
|
319 |
+
}
|
320 |
+
const promptContent = await response.json();
|
321 |
+
setNewPromptContent(promptContent.content);
|
322 |
+
} catch (error) {
|
323 |
+
console.error(error);
|
324 |
+
}
|
325 |
+
};
|
326 |
+
|
327 |
+
fetchPromptContent();
|
328 |
+
}, [selectedPrompt]);
|
329 |
+
|
330 |
+
const handleSaveChanges = () => {
|
331 |
+
fetch(`${apiHost}/api/update_prompt`, {
|
332 |
+
method: 'POST',
|
333 |
+
headers: {
|
334 |
+
'Content-Type': 'application/json',
|
335 |
+
},
|
336 |
+
body: JSON.stringify({
|
337 |
+
id: selectedPrompt.id,
|
338 |
+
name: newPromptName,
|
339 |
+
content: newPromptContent,
|
340 |
+
}),
|
341 |
+
})
|
342 |
+
.then((response) => {
|
343 |
+
if (!response.ok) {
|
344 |
+
throw new Error('Failed to update prompt');
|
345 |
+
}
|
346 |
+
onSelectPrompt(newPromptName, selectedPrompt.id, selectedPrompt.type);
|
347 |
+
setNewPromptName(newPromptName);
|
348 |
+
})
|
349 |
+
.catch((error) => {
|
350 |
+
console.error(error);
|
351 |
+
});
|
352 |
+
};
|
353 |
+
|
354 |
+
return (
|
355 |
+
<div className="mt-[59px]">
|
356 |
+
<div className="mb-4">
|
357 |
+
<p className="font-semibold dark:text-bright-gray">Active Prompt</p>
|
358 |
+
<DropdownPrompt
|
359 |
+
options={prompts}
|
360 |
+
selectedValue={selectedPrompt.name}
|
361 |
+
onSelect={handleSelectPrompt}
|
362 |
+
/>
|
363 |
+
</div>
|
364 |
+
|
365 |
+
<div className="mb-4">
|
366 |
+
<p className='dark:text-bright-gray'>Prompt name </p>{' '}
|
367 |
+
<p className="mb-2 text-xs italic text-eerie-black dark:text-bright-gray">
|
368 |
+
start by editing name
|
369 |
+
</p>
|
370 |
+
<input
|
371 |
+
type="text"
|
372 |
+
value={newPromptName}
|
373 |
+
placeholder="Active Prompt Name"
|
374 |
+
className="w-full rounded-lg border-2 p-2 dark:border-chinese-silver dark:bg-transparent dark:text-white"
|
375 |
+
onChange={(e) => setNewPromptName(e.target.value)}
|
376 |
+
/>
|
377 |
+
</div>
|
378 |
+
|
379 |
+
<div className="mb-4">
|
380 |
+
<p className="mb-2 dark:text-bright-gray">Prompt content</p>
|
381 |
+
<textarea
|
382 |
+
className="h-32 w-full rounded-lg border-2 p-2 dark:border-chinese-silver dark:text-white dark:bg-transparent"
|
383 |
+
value={newPromptContent}
|
384 |
+
onChange={(e) => setNewPromptContent(e.target.value)}
|
385 |
+
placeholder="Active prompt contents"
|
386 |
+
/>
|
387 |
+
</div>
|
388 |
+
|
389 |
+
<div className="flex justify-between">
|
390 |
+
<button
|
391 |
+
className={`rounded-lg bg-green-500 px-4 py-2 font-bold text-white transition-all hover:bg-green-700 ${newPromptName === selectedPrompt.name
|
392 |
+
? 'cursor-not-allowed opacity-50'
|
393 |
+
: ''
|
394 |
+
}`}
|
395 |
+
onClick={handleAddPrompt}
|
396 |
+
disabled={newPromptName === selectedPrompt.name}
|
397 |
+
>
|
398 |
+
Add New Prompt
|
399 |
+
</button>
|
400 |
+
<button
|
401 |
+
className={`rounded-lg bg-red-500 px-4 py-2 font-bold text-white transition-all hover:bg-red-700 ${selectedPrompt.type === 'public'
|
402 |
+
? 'cursor-not-allowed opacity-50'
|
403 |
+
: ''
|
404 |
+
}`}
|
405 |
+
onClick={handleDeletePrompt}
|
406 |
+
disabled={selectedPrompt.type === 'public'}
|
407 |
+
>
|
408 |
+
Delete Prompt
|
409 |
+
</button>
|
410 |
+
<button
|
411 |
+
className={`rounded-lg bg-blue-500 px-4 py-2 font-bold text-white transition-all hover:bg-blue-700 ${selectedPrompt.type === 'public'
|
412 |
+
? 'cursor-not-allowed opacity-50'
|
413 |
+
: ''
|
414 |
+
}`}
|
415 |
+
onClick={handleSaveChanges}
|
416 |
+
disabled={selectedPrompt.type === 'public'}
|
417 |
+
>
|
418 |
+
Save Changes
|
419 |
+
</button>
|
420 |
+
</div>
|
421 |
+
</div>
|
422 |
+
);
|
423 |
+
};
|
424 |
+
|
425 |
+
function DropdownPrompt({
|
426 |
+
options,
|
427 |
+
selectedValue,
|
428 |
+
onSelect,
|
429 |
+
}: {
|
430 |
+
options: { name: string; id: string; type: string }[];
|
431 |
+
selectedValue: string;
|
432 |
+
onSelect: (value: { name: string; id: string; type: string }) => void;
|
433 |
+
}) {
|
434 |
+
const [isOpen, setIsOpen] = useState(false);
|
435 |
+
|
436 |
+
return (
|
437 |
+
<div className="relative mt-2 w-32">
|
438 |
+
<button
|
439 |
+
onClick={() => setIsOpen(!isOpen)}
|
440 |
+
className="flex w-full cursor-pointer items-center rounded-xl border-2 dark:border-chinese-silver bg-white p-3 dark:bg-transparent"
|
441 |
+
>
|
442 |
+
<span className="flex-1 overflow-hidden text-ellipsis dark:text-bright-gray">
|
443 |
+
{selectedValue}
|
444 |
+
</span>
|
445 |
+
<img
|
446 |
+
src={Arrow2}
|
447 |
+
alt="arrow"
|
448 |
+
className={`transform ${isOpen ? 'rotate-180' : 'rotate-0'
|
449 |
+
} h-3 w-3 transition-transform`}
|
450 |
+
/>
|
451 |
+
</button>
|
452 |
+
{isOpen && (
|
453 |
+
<div className="absolute left-0 right-0 z-50 -mt-3 rounded-b-xl border-2 dark:border-chinese-silver bg-white dark:bg-dark-charcoal shadow-lg">
|
454 |
+
{options.map((option, index) => (
|
455 |
+
<div
|
456 |
+
key={index}
|
457 |
+
className="flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:hover:bg-purple-taupe dark:text-bright-gray "
|
458 |
+
>
|
459 |
+
<span
|
460 |
+
onClick={() => {
|
461 |
+
onSelect(option);
|
462 |
+
setIsOpen(false);
|
463 |
+
}}
|
464 |
+
className="ml-2 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3"
|
465 |
+
>
|
466 |
+
{option.name}
|
467 |
+
</span>
|
468 |
+
</div>
|
469 |
+
))}
|
470 |
+
</div>
|
471 |
+
)}
|
472 |
+
</div>
|
473 |
+
);
|
474 |
+
}
|
475 |
+
|
476 |
+
function Dropdown({
|
477 |
+
options,
|
478 |
+
selectedValue,
|
479 |
+
onSelect,
|
480 |
+
showDelete,
|
481 |
+
onDelete,
|
482 |
+
}: {
|
483 |
+
options: string[];
|
484 |
+
selectedValue: string;
|
485 |
+
onSelect: (value: string) => void;
|
486 |
+
showDelete?: boolean; // optional
|
487 |
+
onDelete?: (value: string) => void; // optional
|
488 |
+
}) {
|
489 |
+
const [isOpen, setIsOpen] = useState(false);
|
490 |
+
|
491 |
+
return (
|
492 |
+
<div className="relative mt-2 w-32">
|
493 |
+
<button
|
494 |
+
onClick={() => setIsOpen(!isOpen)}
|
495 |
+
className="flex w-full cursor-pointer items-center rounded-xl border-2 dark:border-chinese-silver bg-white p-3 dark:bg-transparent"
|
496 |
+
>
|
497 |
+
<span className="flex-1 overflow-hidden text-ellipsis dark:text-bright-gray">
|
498 |
+
{selectedValue}
|
499 |
+
</span>
|
500 |
+
<img
|
501 |
+
src={Arrow2}
|
502 |
+
alt="arrow"
|
503 |
+
className={`transform ${isOpen ? 'rotate-180' : 'rotate-0'
|
504 |
+
} h-3 w-3 transition-transform`}
|
505 |
+
/>
|
506 |
+
</button>
|
507 |
+
{isOpen && (
|
508 |
+
<div className="absolute left-0 right-0 z-50 -mt-3 rounded-b-xl border-2 bg-white dark:border-chinese-silver dark:bg-dark-charcoal shadow-lg">
|
509 |
+
{options.map((option, index) => (
|
510 |
+
<div
|
511 |
+
key={index}
|
512 |
+
className="flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:hover:bg-purple-taupe"
|
513 |
+
>
|
514 |
+
<span
|
515 |
+
onClick={() => {
|
516 |
+
onSelect(option);
|
517 |
+
setIsOpen(false);
|
518 |
+
}}
|
519 |
+
className="ml-2 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3 dark:text-light-gray"
|
520 |
+
>
|
521 |
+
{option}
|
522 |
+
</span>
|
523 |
+
{showDelete && onDelete && (
|
524 |
+
<button onClick={() => onDelete(option)} className="p-2">
|
525 |
+
{/* Icon or text for delete button */}
|
526 |
+
Delete
|
527 |
+
</button>
|
528 |
+
)}
|
529 |
+
</div>
|
530 |
+
))}
|
531 |
+
</div>
|
532 |
+
)}
|
533 |
+
</div>
|
534 |
+
);
|
535 |
+
}
|
536 |
+
|
537 |
+
type AddPromptModalProps = {
|
538 |
+
newPromptName: string;
|
539 |
+
onNewPromptNameChange: (name: string) => void;
|
540 |
+
onAddPrompt: () => void;
|
541 |
+
onClose: () => void;
|
542 |
+
};
|
543 |
+
|
544 |
+
const AddPromptModal: React.FC<AddPromptModalProps> = ({
|
545 |
+
newPromptName,
|
546 |
+
onNewPromptNameChange,
|
547 |
+
onAddPrompt,
|
548 |
+
onClose,
|
549 |
+
}) => {
|
550 |
+
return (
|
551 |
+
<div className="fixed top-0 left-0 flex h-screen w-screen items-center justify-center bg-gray-900 bg-opacity-50">
|
552 |
+
<div className="rounded-3xl bg-white p-4">
|
553 |
+
<p className="mb-2 text-2xl font-bold text-jet">Add New Prompt</p>
|
554 |
+
<input
|
555 |
+
type="text"
|
556 |
+
placeholder="Enter Prompt Name"
|
557 |
+
value={newPromptName}
|
558 |
+
onChange={(e) => onNewPromptNameChange(e.target.value)}
|
559 |
+
className="mb-4 w-full rounded-3xl border-2 dark:border-chinese-silver p-2"
|
560 |
+
/>
|
561 |
+
<button
|
562 |
+
onClick={onAddPrompt}
|
563 |
+
className="rounded-3xl bg-purple-300 px-4 py-2 font-bold text-white transition-all hover:bg-purple-600"
|
564 |
+
>
|
565 |
+
Save
|
566 |
+
</button>
|
567 |
+
<button
|
568 |
+
onClick={onClose}
|
569 |
+
className="mt-4 rounded-3xl px-4 py-2 font-bold text-red-500"
|
570 |
+
>
|
571 |
+
Cancel
|
572 |
+
</button>
|
573 |
+
</div>
|
574 |
+
</div>
|
575 |
+
);
|
576 |
+
};
|
577 |
+
|
578 |
+
type DocumentsProps = {
|
579 |
+
documents: Doc[] | null;
|
580 |
+
handleDeleteDocument: (index: number, document: Doc) => void;
|
581 |
+
};
|
582 |
+
|
583 |
+
const Documents: React.FC<DocumentsProps> = ({
|
584 |
+
documents,
|
585 |
+
handleDeleteDocument,
|
586 |
+
}) => {
|
587 |
+
return (
|
588 |
+
<div className="mt-8">
|
589 |
+
<div className="flex flex-col">
|
590 |
+
{/* <h2 className="text-xl font-semibold">Documents</h2> */}
|
591 |
+
|
592 |
+
<div className="mt-[27px] overflow-x-auto border dark:border-chinese-silver rounded-xl w-max">
|
593 |
+
<table className="block w-full table-auto content-center justify-center text-center dark:text-bright-gray" >
|
594 |
+
<thead>
|
595 |
+
<tr>
|
596 |
+
<th className="border-r p-4 md:w-[244px]">Document Name</th>
|
597 |
+
<th className="border-r px-4 py-2 w-[244px]">Vector Date</th>
|
598 |
+
<th className="border-r px-4 py-2 w-[244px]">Type</th>
|
599 |
+
<th className="px-4 py-2"></th>
|
600 |
+
</tr>
|
601 |
+
</thead>
|
602 |
+
<tbody>
|
603 |
+
{documents &&
|
604 |
+
documents.map((document, index) => (
|
605 |
+
<tr key={index}>
|
606 |
+
<td className="border-r border-t px-4 py-2">{document.name}</td>
|
607 |
+
<td className="border-r border-t px-4 py-2">{document.date}</td>
|
608 |
+
<td className="border-r border-t px-4 py-2">
|
609 |
+
{document.location === 'remote'
|
610 |
+
? 'Pre-loaded'
|
611 |
+
: 'Private'}
|
612 |
+
</td>
|
613 |
+
<td className="border-t px-4 py-2">
|
614 |
+
{document.location !== 'remote' && (
|
615 |
+
<img
|
616 |
+
src={Trash}
|
617 |
+
alt="Delete"
|
618 |
+
className="h-4 w-4 cursor-pointer hover:opacity-50"
|
619 |
+
id={`img-${index}`}
|
620 |
+
onClick={(event) => {
|
621 |
+
event.stopPropagation();
|
622 |
+
handleDeleteDocument(index, document);
|
623 |
+
}}
|
624 |
+
/>
|
625 |
+
)}
|
626 |
+
</td>
|
627 |
+
</tr>
|
628 |
+
))}
|
629 |
+
</tbody>
|
630 |
+
</table>
|
631 |
+
</div>
|
632 |
+
{/* <button
|
633 |
+
onClick={toggleAddDocumentModal}
|
634 |
+
className="mt-10 w-32 rounded-lg bg-purple-300 px-4 py-2 font-bold text-white transition-all hover:bg-purple-600"
|
635 |
+
>
|
636 |
+
Add New
|
637 |
+
</button> */}
|
638 |
+
</div>
|
639 |
+
|
640 |
+
{/* {isAddDocumentModalOpen && (
|
641 |
+
<AddDocumentModal
|
642 |
+
newDocument={newDocument}
|
643 |
+
onNewDocumentChange={setNewDocument}
|
644 |
+
onAddDocument={addDocument}
|
645 |
+
onClose={toggleAddDocumentModal}
|
646 |
+
/>
|
647 |
+
)} */}
|
648 |
+
</div>
|
649 |
+
);
|
650 |
+
};
|
651 |
+
|
652 |
+
type Document = {
|
653 |
+
name: string;
|
654 |
+
vectorDate: string;
|
655 |
+
vectorLocation: string;
|
656 |
+
};
|
657 |
+
|
658 |
+
// Modal for adding a new document
|
659 |
+
type AddDocumentModalProps = {
|
660 |
+
newDocument: Document;
|
661 |
+
onNewDocumentChange: (document: Document) => void;
|
662 |
+
onAddDocument: () => void;
|
663 |
+
onClose: () => void;
|
664 |
+
};
|
665 |
+
|
666 |
+
const AddDocumentModal: React.FC<AddDocumentModalProps> = ({
|
667 |
+
newDocument,
|
668 |
+
onNewDocumentChange,
|
669 |
+
onAddDocument,
|
670 |
+
onClose,
|
671 |
+
}) => {
|
672 |
+
return (
|
673 |
+
<div className="fixed top-0 left-0 flex h-screen w-screen items-center justify-center bg-gray-900 bg-opacity-50">
|
674 |
+
<div className="w-[50%] rounded-lg bg-white p-4">
|
675 |
+
<p className="mb-2 text-2xl font-bold text-jet">Add New Document</p>
|
676 |
+
<input
|
677 |
+
type="text"
|
678 |
+
placeholder="Document Name"
|
679 |
+
value={newDocument.name}
|
680 |
+
onChange={(e) =>
|
681 |
+
onNewDocumentChange({ ...newDocument, name: e.target.value })
|
682 |
+
}
|
683 |
+
className="mb-4 w-full rounded-lg border-2 p-2"
|
684 |
+
/>
|
685 |
+
<input
|
686 |
+
type="text"
|
687 |
+
placeholder="Vector Date"
|
688 |
+
value={newDocument.vectorDate}
|
689 |
+
onChange={(e) =>
|
690 |
+
onNewDocumentChange({ ...newDocument, vectorDate: e.target.value })
|
691 |
+
}
|
692 |
+
className="mb-4 w-full rounded-lg border-2 p-2"
|
693 |
+
/>
|
694 |
+
<input
|
695 |
+
type="text"
|
696 |
+
placeholder="Vector Location"
|
697 |
+
value={newDocument.vectorLocation}
|
698 |
+
onChange={(e) =>
|
699 |
+
onNewDocumentChange({
|
700 |
+
...newDocument,
|
701 |
+
vectorLocation: e.target.value,
|
702 |
+
})
|
703 |
+
}
|
704 |
+
className="mb-4 w-full rounded-lg border-2 p-2"
|
705 |
+
/>
|
706 |
+
<button
|
707 |
+
onClick={onAddDocument}
|
708 |
+
className="rounded-lg bg-purple-300 px-4 py-2 font-bold text-white transition-all hover:bg-purple-600"
|
709 |
+
>
|
710 |
+
Save
|
711 |
+
</button>
|
712 |
+
<button
|
713 |
+
onClick={onClose}
|
714 |
+
className="mt-4 rounded-lg px-4 py-2 font-bold text-red-500"
|
715 |
+
>
|
716 |
+
Cancel
|
717 |
+
</button>
|
718 |
+
</div>
|
719 |
+
</div>
|
720 |
+
);
|
721 |
+
};
|
722 |
+
|
723 |
+
const Widgets: React.FC<{
|
724 |
+
widgetScreenshot: File | null;
|
725 |
+
onWidgetScreenshotChange: (screenshot: File | null) => void;
|
726 |
+
}> = ({ widgetScreenshot, onWidgetScreenshotChange }) => {
|
727 |
+
const widgetSources = ['Source 1', 'Source 2', 'Source 3'];
|
728 |
+
const widgetMethods = ['Method 1', 'Method 2', 'Method 3'];
|
729 |
+
const widgetTypes = ['Type 1', 'Type 2', 'Type 3'];
|
730 |
+
|
731 |
+
const [selectedWidgetSource, setSelectedWidgetSource] = useState(
|
732 |
+
widgetSources[0],
|
733 |
+
);
|
734 |
+
const [selectedWidgetMethod, setSelectedWidgetMethod] = useState(
|
735 |
+
widgetMethods[0],
|
736 |
+
);
|
737 |
+
const [selectedWidgetType, setSelectedWidgetType] = useState(widgetTypes[0]);
|
738 |
+
|
739 |
+
// const [widgetScreenshot, setWidgetScreenshot] = useState<File | null>(null);
|
740 |
+
const [widgetCode, setWidgetCode] = useState<string>(''); // Your widget code state
|
741 |
+
|
742 |
+
const handleScreenshotChange = (
|
743 |
+
event: React.ChangeEvent<HTMLInputElement>,
|
744 |
+
) => {
|
745 |
+
const files = event.target.files;
|
746 |
+
|
747 |
+
if (files && files.length > 0) {
|
748 |
+
const selectedScreenshot = files[0];
|
749 |
+
onWidgetScreenshotChange(selectedScreenshot); // Update the screenshot in the parent component
|
750 |
+
}
|
751 |
+
};
|
752 |
+
|
753 |
+
const handleCopyToClipboard = () => {
|
754 |
+
// Create a new textarea element to select the text
|
755 |
+
const textArea = document.createElement('textarea');
|
756 |
+
textArea.value = widgetCode;
|
757 |
+
document.body.appendChild(textArea);
|
758 |
+
|
759 |
+
// Select and copy the text
|
760 |
+
textArea.select();
|
761 |
+
document.execCommand('copy');
|
762 |
+
|
763 |
+
// Clean up the textarea element
|
764 |
+
document.body.removeChild(textArea);
|
765 |
+
};
|
766 |
+
|
767 |
+
return (
|
768 |
+
<div>
|
769 |
+
<div className="mt-[59px]">
|
770 |
+
<p className="font-bold text-jet">Widget Source</p>
|
771 |
+
<Dropdown
|
772 |
+
options={widgetSources}
|
773 |
+
selectedValue={selectedWidgetSource}
|
774 |
+
onSelect={setSelectedWidgetSource}
|
775 |
+
/>
|
776 |
+
</div>
|
777 |
+
<div className="mt-5">
|
778 |
+
<p className="font-bold text-jet">Widget Method</p>
|
779 |
+
<Dropdown
|
780 |
+
options={widgetMethods}
|
781 |
+
selectedValue={selectedWidgetMethod}
|
782 |
+
onSelect={setSelectedWidgetMethod}
|
783 |
+
/>
|
784 |
+
</div>
|
785 |
+
<div className="mt-5">
|
786 |
+
<p className="font-bold text-jet">Widget Type</p>
|
787 |
+
<Dropdown
|
788 |
+
options={widgetTypes}
|
789 |
+
selectedValue={selectedWidgetType}
|
790 |
+
onSelect={setSelectedWidgetType}
|
791 |
+
/>
|
792 |
+
</div>
|
793 |
+
<div className="mt-6">
|
794 |
+
<p className="font-bold text-jet">Widget Code Snippet</p>
|
795 |
+
<textarea
|
796 |
+
rows={4}
|
797 |
+
value={widgetCode}
|
798 |
+
onChange={(e) => setWidgetCode(e.target.value)}
|
799 |
+
className="mt-3 w-full rounded-lg border-2 p-2"
|
800 |
+
/>
|
801 |
+
</div>
|
802 |
+
<div className="mt-1">
|
803 |
+
<button
|
804 |
+
onClick={handleCopyToClipboard}
|
805 |
+
className="rounded-lg bg-blue-400 px-2 py-2 font-bold text-white transition-all hover:bg-blue-600"
|
806 |
+
>
|
807 |
+
Copy
|
808 |
+
</button>
|
809 |
+
</div>
|
810 |
+
|
811 |
+
<div className="mt-4">
|
812 |
+
<p className="text-lg font-semibold">Widget Screenshot</p>
|
813 |
+
<input type="file" accept="image/*" onChange={handleScreenshotChange} />
|
814 |
+
</div>
|
815 |
+
|
816 |
+
{widgetScreenshot && (
|
817 |
+
<div className="mt-4">
|
818 |
+
<img
|
819 |
+
src={URL.createObjectURL(widgetScreenshot)}
|
820 |
+
alt="Widget Screenshot"
|
821 |
+
className="max-w-full rounded-lg border border-gray-300"
|
822 |
+
/>
|
823 |
+
</div>
|
824 |
+
)}
|
825 |
+
</div>
|
826 |
+
);
|
827 |
+
};
|
src/assets/add.svg
ADDED
|
src/assets/alert.svg
ADDED
|
src/assets/arrow-down.svg
ADDED
|
src/assets/arrow-left.svg
ADDED
|
src/assets/arrow-right.svg
ADDED
|
src/assets/arrow.svg
ADDED
|
src/assets/checkMark2.svg
ADDED
|
src/assets/checkmark.svg
ADDED
|
src/assets/copy.svg
ADDED
|
src/assets/cute_docsgpt3.svg
ADDED
|
src/assets/discord-dark.svg
ADDED
|
src/assets/discord.svg
ADDED
|
src/assets/dislike.svg
ADDED
|
src/assets/documentation-dark.svg
ADDED
|
src/assets/documentation.svg
ADDED
|
src/assets/dropdown-arrow.svg
ADDED
|
src/assets/edit.svg
ADDED
|
src/assets/exit.svg
ADDED
|
src/assets/expand.svg
ADDED
|
src/assets/github-dark.svg
ADDED
|
src/assets/github.svg
ADDED
|
src/assets/hamburger-dark.svg
ADDED
|
src/assets/hamburger.svg
ADDED
|