VTechAI commited on
Commit
fed832e
1 Parent(s): 8f04dcf
This view is limited to 50 files because it contains too many changes.   See raw diff
.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&apos;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