Compare commits

..

238 Commits
0.6.0 ... 0.8.1

Author SHA1 Message Date
Alex
e07df29ab9 Update FLASK_DEBUG_MODE setting to use value from settings module 2024-04-08 13:27:43 +01:00
Alex
abf24fe60f Update FLASK_DEBUG_MODE setting to use value from settings module 2024-04-08 13:15:58 +01:00
Alex
6911f8652a Fix vectorstore path in check_docs function 2024-04-08 13:06:05 +01:00
Alex
6658cec6a0 Merge pull request #897 from arc53/dependabot/npm_and_yarn/frontend/vite-5.0.13
Bump vite from 5.0.12 to 5.0.13 in /frontend
2024-04-08 13:03:20 +01:00
Alex
14011b9d84 Merge pull request #891 from arc53/dependabot/npm_and_yarn/mock-backend/express-4.19.2
Bump express from 4.18.2 to 4.19.2 in /mock-backend
2024-04-08 13:02:58 +01:00
Alex
bd2d0b6790 Merge pull request from GHSA-p5qc-vj2x-9rjp
advisory-fix
2024-04-08 12:58:36 +01:00
Alex
d36f58230a advisory-fix 2024-04-08 12:56:27 +01:00
Alex
018f950ca3 Merge pull request #908 from arc53/api-keys-documentation-guide
API keys guide
2024-04-08 10:36:35 +01:00
Alex
db8db9fae9 Add prompt_id and chunks fields in create_api_key function 2024-04-08 10:35:15 +01:00
Pavel
79ce8d6563 guide 2024-04-07 20:14:16 +04:00
Alex
13eaa9a35a Merge pull request #904 from arc53/fix/update-docs-widget
Update api key to new data
2024-04-06 11:39:32 +01:00
Alex
6e147b3ed2 Update api key to new data 2024-04-05 14:49:32 +01:00
Alex
c162f79daa Merge pull request #903 from arc53/feature/api-key-create
Feature/api key create
2024-04-05 13:18:11 +01:00
Alex
87585be687 Merge branch 'main' into feature/api-key-create 2024-04-05 13:01:42 +01:00
Alex
ea08d6413c Merge pull request #902 from ManishMadan2882/feature/api-key-create
Add Prompt, Chunks in Create Key
2024-04-04 12:45:33 +01:00
Alex
879905edf6 Refactor create_api_key function to include prompt_id and chunks in routes.py 2024-04-04 12:38:23 +01:00
Alex
6fd80a5582 Merge pull request #899 from siiddhantt/main
feat: added prompts section under general in settings
2024-04-04 10:25:08 +01:00
Siddhant Rai
0dc7333563 fix: added API Keys in tabs 2024-04-04 14:42:14 +05:30
Siddhant Rai
f61c3168d2 fix: issue with editing new prompts 2024-04-04 14:29:37 +05:30
Siddhant Rai
9cadd74a96 fix: minor ui changes 2024-04-04 13:42:32 +05:30
Siddhant Rai
729fa2352b feat: added prompts section under general in settings 2024-04-04 00:48:49 +05:30
dependabot[bot]
b673aaf9f0 Bump vite from 5.0.12 to 5.0.13 in /frontend
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.12 to 5.0.13.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.13/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.13/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-03 17:53:09 +00:00
Alex
3132cc6005 Merge pull request #895 from sarfarazsiddiquii/new_branch
added feature #887
2024-04-03 17:09:06 +01:00
ManishMadan2882
ac994d3077 add prompt,chunks in create key 2024-04-03 19:19:53 +05:30
sarfaraz siddiqui
02d4f7f2da functions can accept null 2024-04-03 18:08:46 +05:30
Alex
d99569f005 Merge pull request #896 from arc53/api-update
api update
2024-04-01 18:22:13 +01:00
Pavel
ec5166249a api update
A description of 3 more api methods in documentation.
2024-04-01 21:07:27 +04:00
Alex
dadd12adb3 Update API key in DocsGPTWidget.tsx 2024-04-01 11:25:59 +01:00
Alex
88b4fb8c2a Update API key in DocsGPTWidget.tsx 2024-04-01 11:25:31 +01:00
sarfaraz siddiqui
afecae3786 added feature #887 2024-03-31 03:50:11 +05:30
Alex
d18598bc33 Merge pull request #894 from arc53/feature/api-key-create
Feature/api key create
2024-03-29 20:04:26 +00:00
Alex
794fc05ada Merge branch 'main' into feature/api-key-create 2024-03-29 19:59:45 +00:00
Alex
5daeb7f876 Merge pull request #892 from ManishMadan2882/feature/api-key-create
Feature/api key create
2024-03-29 19:57:25 +00:00
ManishMadan2882
53e71c545e api key modal - enhancements 2024-03-29 19:11:40 +05:30
ManishMadan2882
959a55e36c adding dark mode - api key 2024-03-29 04:13:12 +05:30
ManishMadan2882
64572b0024 feat(settings): api key endpoints 2024-03-29 03:26:45 +05:30
Manish Madan
9a0c1caa43 Merge branch 'arc53:feature/api-key-create' into feature/api-key-create 2024-03-28 19:28:23 +05:30
ManishMadan2882
eed6723147 feat(settings): api keys tab 2024-03-28 19:25:35 +05:30
Alex
97fabf51b8 Refactor conversationSlice.ts and conversationApi.ts 2024-03-28 13:43:10 +00:00
Alex
5e5e2b8aee Merge pull request #877 from siiddhantt/main
Added reddit loader
2024-03-27 16:55:01 +00:00
Siddhant Rai
e01071426f feat: field to pass number of posts as a parameter 2024-03-27 19:20:55 +05:30
Siddhant Rai
eed1bfbe50 feat: fields to handle reddit loader + minor changes 2024-03-26 16:07:44 +05:30
Siddhant Rai
0c3970a266 Merge branch 'arc53:main' into main 2024-03-26 16:07:25 +05:30
dependabot[bot]
267cfb621e Bump express from 4.18.2 to 4.19.2 in /mock-backend
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-26 10:25:02 +00:00
Alex
0e90febab2 Merge pull request #890 from arc53/dependabot/npm_and_yarn/docs/katex-0.16.10
Bump katex from 0.16.9 to 0.16.10 in /docs
2024-03-26 10:24:19 +00:00
dependabot[bot]
31d947837f Bump katex from 0.16.9 to 0.16.10 in /docs
Bumps [katex](https://github.com/KaTeX/KaTeX) from 0.16.9 to 0.16.10.
- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.16.9...v0.16.10)

---
updated-dependencies:
- dependency-name: katex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 20:31:43 +00:00
Alex
017b11fbba Merge pull request #888 from arc53/fix/parsing-chunks-issue
Fix parsing issue with chunks in store.ts
2024-03-23 11:47:11 +00:00
Alex
3c492062a9 Fix parsing issue with chunks in store.ts 2024-03-23 11:42:50 +00:00
Alex
b26b49d0ca Merge pull request #883 from arc53/feat/chunks
Add support for setting the number of chunks processed per query
2024-03-22 15:34:09 +00:00
Alex
ed08123550 Add support for setting the number of chunks processed per query 2024-03-22 14:50:56 +00:00
Alex
add2db5b7a Merge pull request #881 from arc53/fix_model_selection_for_openai
Fix model selection at least for openAI LLM_NAME
2024-03-21 16:47:52 +00:00
Siddhant Rai
f272d7121a Merge branch 'arc53:main' into main 2024-03-21 19:38:44 +05:30
Anton Larin
577556678c Fix model selection at least for openAI LLM_NAME 2024-03-21 10:14:48 +01:00
Alex
e146922367 Merge pull request #880 from ManishMadan2882/main
Customised Scrollbar, fixed: Hero wasn't completely scrollable in Mobile
2024-03-20 10:08:22 +00:00
ManishMadan2882
6f1548b7f8 customised scrollbar 2024-03-19 21:40:00 +05:30
ManishMadan2882
9e6fe47b44 fix(hero): not fully scrollable in mobile 2024-03-19 21:39:16 +05:30
Siddhant Rai
60cfea1126 feat: added reddit loader 2024-03-16 20:22:05 +05:30
Alex
80a4a094af lint 2024-03-14 11:37:33 +00:00
Alex
70e1560cb3 fix check on model 2024-03-14 11:37:01 +00:00
Alex
725033659a Merge pull request #876 from ManishMadan2882/main
Pause Auto-scroll on user interrupt
2024-03-14 11:33:07 +00:00
ManishMadan2882
059111fb57 widget: version release 2024-03-14 16:58:57 +05:30
ManishMadan2882
d4a5eadf13 docs: updated version 2024-03-14 16:54:52 +05:30
ManishMadan2882
79cf487ac5 purge unused deps, comments 2024-03-14 04:03:17 +05:30
ManishMadan2882
52ecbab859 purge logs 2024-03-14 04:01:35 +05:30
ManishMadan2882
adfc79bf92 block autoScroll on user interrupt 2024-03-14 02:25:33 +05:30
ManishMadan2882
2447bab924 add listener for wheel, touch events 2024-03-14 01:51:55 +05:30
Alex
1057ca78a6 default remote 2024-03-13 17:01:23 +00:00
ManishMadan2882
7e7f98fd92 sanitize html - add dompurify 2024-03-13 00:21:54 +05:30
ManishMadan2882
64552ce2de add snarkdown: markdown support 2024-03-12 18:12:27 +05:30
ManishMadan2882
7506256f42 fix(lint): 2 errors 2024-03-11 19:42:22 +05:30
ManishMadan2882
db75230521 pause scroll on user action 2024-03-11 19:21:17 +05:30
Alex
f8955d5607 Update README.md 2024-03-11 12:05:35 +00:00
Alex
0bad217b93 Merge pull request #867 from siiddhantt/main
fix: issue #157
2024-03-08 16:06:51 +00:00
Alex
4da400a136 Merge pull request #873 from ManishMadan2882/main 2024-03-07 19:26:33 +00:00
ManishMadan2882
24740bd341 fix(UI) overflow in next 2024-03-08 00:48:18 +05:30
ManishMadan2882
3b6a15de84 version update 2024-03-08 00:46:53 +05:30
ManishMadan2882
ac1f525a6c fix: fine tuned the css 2024-03-07 19:20:03 +05:30
ManishMadan2882
e3999bdb0c updating version 2024-03-07 19:14:55 +05:30
ManishMadan2882
ad3d5a30ec docs update - widget v0.3.3 2024-03-07 15:58:31 +05:30
Alex
e4b5847725 Merge pull request #872 from ManishMadan2882/main
Widget UI fixes
2024-03-07 09:54:51 +00:00
ManishMadan2882
1a91a245a3 ui fixes 2024-03-07 02:50:30 +05:30
Alex
229f62d071 Merge pull request #861 from ManishMadan2882/main
DocsGPT Widget
2024-03-06 17:38:47 +00:00
Alex
b96fe16770 Update docsgpt version to 0.3.0 2024-03-06 17:36:47 +00:00
Alex
97750cb5e2 Update package.json with new version and add repository information 2024-03-06 17:24:23 +00:00
Siddhant Rai
e1a2bd11a9 fix: upload dropdown also combined 2024-03-06 16:01:53 +05:30
ManishMadan2882
229b408252 adding fallback avatar 2024-03-06 01:58:52 +05:30
ManishMadan2882
ae929438a5 shifted to parcel, styled-components 2024-03-05 21:15:58 +05:30
Siddhant Rai
5daaf84e05 fix: combined two dropdowns into a single component 2024-03-05 14:26:08 +05:30
Siddhant Rai
19b09515a1 Merge branch 'arc53:main' into main 2024-03-05 14:22:51 +05:30
Alex
9ce6078c8b Merge pull request #863 from Anush008/main
feat: Qdrant vectorstore support
2024-03-04 12:41:11 +00:00
Siddhant Rai
51f588f4b1 fix: issue #157 2024-03-04 15:45:34 +05:30
Alex
5ee6605703 Merge pull request #835 from arc53/feature/remote-loads
Feature/remote loads
2024-03-01 15:42:42 +00:00
Alex
7ef97cfd81 fix abort 2024-03-01 15:42:22 +00:00
Alex
f4288f0bd4 remove sitemap 2024-03-01 14:41:03 +00:00
Alex
4a701cb993 Merge branch 'main' into feature/remote-loads 2024-03-01 14:38:27 +00:00
Anush008
00dfb07b15 chore: revert to faiss default 2024-02-29 09:48:38 +05:30
ManishMadan2882
5fffa8e9db adding rollup-plugin-import-css 2024-02-29 04:11:47 +05:30
Pavel
54d187a0ad Fixing ingestion metadata grouping 2024-02-28 19:52:58 +03:00
ManishMadan2882
192ce468b7 inline responsive module styles 2024-02-28 19:31:36 +05:30
Anush008
75c0cadb50 feat: Qdrant vector store 2024-02-28 11:49:15 +05:30
ManishMadan2882
5d578d4b3b preparing for npm publish 2024-02-27 21:31:08 +05:30
Alex
325a8889ab update url 2024-02-27 11:52:51 +00:00
ManishMadan2882
9cdd78e68c purge out dist, update gitignore 2024-02-27 15:51:05 +05:30
ManishMadan2882
3a6770a1ae preparing build 2024-02-26 21:10:22 +05:30
ManishMadan2882
8073924056 padding improved at the edges 2024-02-26 20:46:46 +05:30
ManishMadan2882
7b53e1c54b UI enhancement, scroll fix 2024-02-26 20:10:00 +05:30
Alex
c4c0516820 add endpoint 2024-02-26 14:31:54 +00:00
Alex
8d36f8850e Merge pull request #860 from arc53/Fix-ingestion-grouping
Fixing ingestion metadata grouping
2024-02-26 10:16:37 +00:00
ManishMadan2882
abe5f43f3d adding responsive markdown response, error alert 2024-02-26 03:15:31 +05:30
Pavel
c8d8a8d0b5 Fixing ingestion metadata grouping 2024-02-25 16:03:18 +03:00
ManishMadan2882
f60e88573a refactored UI strategy, added prompt response in chat box 2024-02-24 21:02:28 +05:30
Alex
4216671ea2 Update README.md 2024-02-24 12:28:31 +00:00
Alex
ee3ea7a970 Add wget and unzip packages to Dockerfile 2024-02-23 21:19:04 +00:00
Alex
2b644dbb01 Add Rust toolchain and download mpnet-base-v2.zip model 2024-02-23 21:15:26 +00:00
ManishMadan2882
63878e7ffd inititated shadcn 2024-02-19 04:14:09 +05:30
Alex
007cd6cff1 Add conversations to db.json 2024-02-18 19:33:45 +00:00
Alex
4375215baa Update port number in Dockerfile and server.js 2024-02-18 19:12:58 +00:00
Alex
8cc5e9db13 Merge pull request #856 from ManishMadan2882/main
(mock) adding prompt routes
2024-02-16 11:22:40 +00:00
ManishMadan2882
5685f831a7 (mock) adding prompt routes 2024-02-15 05:35:34 +05:30
Alex
0cb3d12d94 Refactor loader classes to accept inputs directly 2024-02-14 15:17:56 +00:00
Alex
0e38c6751b Merge pull request #854 from ManishMadan2882/main
Message Streaming with the Mock Server
2024-02-14 13:50:15 +00:00
ManishMadan2882
70ad1fb3d8 Merge branch 'main' of https://github.com/manishMadan2882/docsgpt 2024-02-14 18:50:02 +05:30
ManishMadan2882
44f27d91a0 purge console logs 2024-02-14 18:48:43 +05:30
Manish Madan
1bb559c285 Merge branch 'arc53:main' into main 2024-02-14 18:40:24 +05:30
ManishMadan2882
7a005ef126 streamed the sample response /stream 2024-02-14 18:39:21 +05:30
Pavel
030c2a740f upload_remote class 2024-02-13 23:41:36 +03:00
Alex
5dcde67ae9 Merge pull request #852 from arc53/feat/premaillm
fix: docsgpt provider
2024-02-13 15:20:05 +00:00
Alex
ee06fa85f1 fix: docsgpt provider 2024-02-13 15:06:52 +00:00
Alex
5b9352a946 Merge pull request #851 from arc53/feat/premaillm
Add PremAI LLM implementation
2024-02-13 14:14:20 +00:00
Alex
b7927d8d75 Add PremAI LLM implementation 2024-02-13 14:08:55 +00:00
Alex
c144f30606 Merge pull request #850 from ManishMadan2882/feature/remote-loads
adding remote uploads tab
2024-02-12 23:46:30 +00:00
ManishMadan2882
d2dba3a0db adding remote uploads tab 2024-02-13 01:53:25 +05:30
Alex
2c991583ff Merge pull request #848 from ManishMadan2882/main
Makes input field absolute in Conversation, fixes delete icon in Settings/Documents
2024-02-09 14:20:02 +00:00
Alex
2e14dec12d Merge pull request #849 from arc53/main
Sync
2024-02-09 14:05:39 +00:00
ManishMadan2882
8826f0ff3c slight UI improvements in input box 2024-02-09 19:17:26 +05:30
ManishMadan2882
9129f7fb33 fix(Conversation): input box UI 2024-02-09 19:12:48 +05:30
ManishMadan2882
c0ed54406f fix(settings): delete button 2024-02-09 18:04:24 +05:30
Alex
18be257e10 Merge pull request #847 from ManishMadan2882/main
Fix : error on changing conversation while streaming answer
2024-02-07 18:00:12 +00:00
ManishMadan2882
615d549494 slight fixes, checking for null case 2024-02-07 05:09:12 +05:30
ManishMadan2882
0ce39e7f52 purge logs and !need code 2024-02-07 05:04:16 +05:30
ManishMadan2882
3c68cbc955 fix(stream err on changing conversation) 2024-02-07 04:42:39 +05:30
ManishMadan2882
300430e2d5 fixes weird bug- dark theme hook 2024-02-06 05:17:43 +05:30
Alex
166a07732a Merge pull request #820 from Quentium-Forks/main
Bump dependencies & support next 14 for docs
2024-02-05 15:13:40 +00:00
Alex
510b517270 Merge pull request #844 from ManishMadan2882/main
Fix: Sidebar Icons update on changing theme
2024-02-01 09:55:50 +00:00
ManishMadan2882
dea385384a fixes, update Nav images on theme toggle 2024-02-01 03:43:05 +05:30
ManishMadan2882
7a1c9101b2 add custom hook for dark theme 2024-02-01 03:42:09 +05:30
Alex
2be523cf77 Fix handling of embeddings_key in api_search() function 2024-01-30 17:22:33 +00:00
Alex
c01e334487 Merge pull request #843 from larinam/fix_application
Fix application + script requirements.txt
2024-01-29 20:59:36 +00:00
Alex
a2418d1373 Add sentence-transformers library to requirements.txt and comment out model_name in base.py 2024-01-29 20:51:28 +00:00
Alex
a697248b26 Merge pull request #841 from ManishMadan2882/main
Message bubble responsiveness
2024-01-29 13:55:29 +00:00
ManishMadan2882
6058939c00 change size in copy, like , dislike icons 2024-01-29 19:10:03 +05:30
Anton Larin
318de530e3 fix openapi-parser requirement 2024-01-27 16:52:33 +01:00
Anton Larin
9e04b7796a application folder related changes:
* optimize content of requirements.txt
* upgrade libs
* fix imports
2024-01-27 16:25:19 +01:00
Anton Larin
e8099c4db5 script folder related changes:
* optmize content of requirements.txt
* upgrade libs
* fix imports
2024-01-27 14:58:08 +01:00
Alex
bf808811cc Update README.md 2024-01-26 12:21:09 +00:00
ManishMadan2882
f0293de1b9 ui adjustments 2024-01-26 03:06:15 +05:30
ManishMadan2882
810dcb90ce refactored the divs, prevent overlap 2024-01-26 02:47:51 +05:30
ManishMadan2882
a2f2b8fabc make responsive msg bubble 2024-01-26 02:33:50 +05:30
Alex
cbc5f47786 Merge pull request #837 from ManishMadan2882/main
Adding Dark Mode
2024-01-23 14:59:22 +00:00
ManishMadan2882
3e3886ced7 slight UI changes 2024-01-23 19:38:22 +05:30
ManishMadan2882
9ce39fd2ba made borders in settings a bit darker 2024-01-22 18:22:46 +05:30
ManishMadan2882
5b08cdedf0 revert changes in docker yaml 2024-01-22 16:22:19 +05:30
ManishMadan2882
67e4d40c49 added dark mode, About page and bubble icons 2024-01-22 16:11:26 +05:30
ManishMadan2882
537a733157 add dark mode - conversation, bubble, UI fixes 2024-01-22 02:56:07 +05:30
ManishMadan2882
5136e7726d added dark mode, Hero component 2024-01-21 17:18:23 +05:30
Alex
6e236ba74d Merge pull request #827 from Juneezee/vite-5
Upgrade to Vite 5
2024-01-19 14:01:40 +00:00
Eng Zer Jun
374b665089 Upgrade to Vite 5
This commit upgrades vite to the latest version 5, and also updates the
vite plugins to the latest version.

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2024-01-19 21:34:28 +08:00
ManishMadan2882
ffecc9a0c7 add dark mode, in Settings 2024-01-19 19:01:13 +05:30
ManishMadan2882
0b997418d3 add dark - sidebar 2024-01-19 01:47:23 +05:30
ManishMadan2882
eaad8a4cf5 initialising dark mode 2024-01-18 02:39:40 +05:30
Alex
396b4595f4 Merge pull request #832 from ArnabBCA/main
Fixed Empty Document Name Upload
2024-01-16 17:07:02 +00:00
Arnab Ghosh
0752aae9ef Fixed Empty Document Name Upload 2024-01-16 15:35:48 +05:30
Alex
ad2221a677 Merge pull request #830 from arc53/feature/search-endpoint
Search docs not inside the /stream in the stream request
2024-01-15 16:55:53 +00:00
Alex
1713d693b1 Merge pull request #831 from ManishMadan2882/feature/search-endpoint
integrate /api/search endpoint, get sources post stream
2024-01-15 16:34:48 +00:00
ManishMadan2882
f4f056449f integrate /api/search endpoint, get sources post stream 2024-01-15 20:23:18 +05:30
Alex
6a70e3e45b Commented out unused code in api_search function 2024-01-12 14:39:17 +00:00
Alex
a04cdee33f Refactor source log generation in complete_stream function 2024-01-12 14:38:15 +00:00
Alex
157769eeb4 Add API endpoint for searching documents 2024-01-12 14:35:23 +00:00
Alex
667b66b926 Merge pull request #825 from arc53/feat/mongodb
Public LLM
2024-01-09 14:31:40 +00:00
Alex
c0f7b344d9 Update environment variables and installation instructions 2024-01-09 12:35:18 +00:00
Alex
060c59e97d Update mpnet-base-v2.zip download URL 2024-01-09 11:41:25 +00:00
Alex
b3461b7134 Add MPNet model and update vector store for Hugging Face embeddings 2024-01-09 11:39:32 +00:00
Pavel
001c450abb choice text 2024-01-09 13:05:16 +03:00
Pavel
ceaa5763d4 choice fix 2024-01-09 12:57:19 +03:00
Alex
b45fd58944 Update EMBEDDINGS_NAME in settings.py and test_vector_store.py 2024-01-09 00:34:04 +00:00
Alex
b3149def82 Update EMBEDDINGS_NAME in settings.py 2024-01-09 00:29:02 +00:00
Alex
378d498402 Remove unused imports in docsgpt_provider.py 2024-01-09 00:19:49 +00:00
Alex
98f52b32a3 Update README and Quickstart guide 2024-01-09 00:18:04 +00:00
Alex
0ab32a6f84 Update setup.sh script with new options for language model usage 2024-01-09 00:07:37 +00:00
Alex
71cc22325d Add application files and update setup script 2024-01-09 00:05:44 +00:00
Alex
e1b2991aa6 Update LLM_NAME and EMBEDDINGS_NAME 2024-01-09 00:01:31 +00:00
Alex
033bcf80d0 docsgpt llm provider 2024-01-08 23:35:37 +00:00
Alex
103118d558 Merge pull request #823 from ManishMadan2882/main
fix distortion on different browsers
2024-01-08 11:30:30 +00:00
ManishMadan2882
f91b5fa004 fix distortion on different browsers 2024-01-08 16:03:41 +05:30
Alex
7179bf7b67 Merge pull request #822 from arc53/feat/mongodb
Mongodb integration as vectorstore
2024-01-06 18:30:25 +00:00
Alex
a3e6239e6e fix: remove import 2024-01-06 18:23:20 +00:00
Alex
1fa12e56c6 Remove unused test cases in test_openai.py 2024-01-06 18:04:50 +00:00
Alex
4ff834de76 Refactor MongoDBVectorStore and add delete_index method 2024-01-06 17:59:01 +00:00
QuentiumYT
6db38ad769 Bump dependencies & support next 14 for docs
- Renamed _app.js to mdx (for Next 14)
- Lint next config file & package.json
2024-01-05 18:50:49 +01:00
Alex
293b7b09a9 init tests 2024-01-05 17:16:16 +00:00
Alex
d5945f9ee7 Update README.md 2024-01-05 13:58:22 +00:00
Alex
d1f5a6fc31 Merge pull request #816 from ManishMadan2882/main
adding responsive sidebar
2024-01-04 20:51:30 +00:00
ManishMadan2882
e7b9f5e4c3 adding responsive sidebar 2024-01-05 01:50:52 +05:30
Alex
7870749077 fix openai 2024-01-03 12:09:05 +00:00
Alex
c5352f443a Merge pull request #813 from CBID2/making-alt-text-less-redundant
fix: Making alt text less redundant
2024-01-03 10:52:08 +00:00
Christine
fd8b7aa0f2 fix: change alt text for setting 2024-01-03 04:17:53 +00:00
Christine
458ea266ec fix: change name to alt text for Discord and GitHub 2024-01-03 04:14:06 +00:00
Alex
9748eaba25 Merge pull request #811 from Rutam21/patch-2
Added new Deployment Guide for Kamatera Performance Cloud
2023-12-31 15:37:32 +00:00
Alex
887a3740b2 Update holopin.yml 2023-12-31 15:34:55 +00:00
Rutam Prita Mishra
2e7cfe9cd7 Added new Deployment Guide
This PR adds a new deployment guide for Kamatera Performance Cloud.
2023-12-26 16:28:57 +05:30
Alex
6dbe156a02 Update README.md 2023-12-25 11:52:37 +00:00
Alex
2a9ef6d48e Merge pull request #792 from arc53/dependabot/npm_and_yarn/frontend/vite-4.5.1
Bump vite from 4.5.0 to 4.5.1 in /frontend
2023-12-22 15:51:11 +00:00
Alex
6717ddbd0b Merge pull request #804 from arc53/dependabot/npm_and_yarn/docs/next-13.5.1
Bump next from 13.4.19 to 13.5.1 in /docs
2023-12-22 15:50:49 +00:00
Alex
47c1aab064 Merge pull request #793 from arc53/dependabot/npm_and_yarn/extensions/react-widget/vite-4.4.12
Bump vite from 4.4.9 to 4.4.12 in /extensions/react-widget
2023-12-22 15:50:08 +00:00
Alex
eda41658b9 Merge pull request #806 from arc53/cve/py-removal
fix: Remove py==1.11.0 from requirements.txt
2023-12-22 15:47:45 +00:00
Alex
7f79363944 fix: Remove py==1.11.0 from requirements.txt 2023-12-22 15:44:39 +00:00
Alex
25967f2a09 Merge pull request #805 from arc53/fix/scripts-vulnerabilities
fix: vulns
2023-12-22 15:35:37 +00:00
Alex
4d3963ad67 fix: vulns 2023-12-22 15:27:23 +00:00
dependabot[bot]
f78c5257dc Bump next from 13.4.19 to 13.5.1 in /docs
Bumps [next](https://github.com/vercel/next.js) from 13.4.19 to 13.5.1.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v13.4.19...v13.5.1)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-22 14:14:27 +00:00
Alex
ccc6234ac8 Merge pull request #803 from arc53/fix/dependency-upgrades
fix: cve upgrades
2023-12-22 14:13:41 +00:00
Alex
c81b0200eb fix: Update pydantic_settings version to 2.1.0 2023-12-22 14:08:03 +00:00
Alex
f039d37c8a fix: pydantic 2023-12-22 14:03:43 +00:00
Alex
237975bfef fix: cve upgrades 2023-12-22 13:25:57 +00:00
Alex
015bc7c8c3 hotfix source doc data 2023-12-22 12:10:35 +00:00
Alex
3da2a00ee9 Merge pull request #801 from Victorivus/bug/fix-#800-mistral_models
Update requirements.txt HF Transformers
2023-12-14 15:29:28 +00:00
Victorivus
16eca5bebf Update requirements.txt HF Transformers
Fix 'mistral' models missing
2023-12-12 15:16:31 +01:00
dependabot[bot]
f8ac5e0af3 Bump vite from 4.4.9 to 4.4.12 in /extensions/react-widget
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.9 to 4.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-06 01:52:56 +00:00
dependabot[bot]
eb48a153d9 Bump vite from 4.5.0 to 4.5.1 in /frontend
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.1/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-05 23:44:40 +00:00
Pavel
381a2740ee change input 2023-10-13 21:52:56 +04:00
Alex
8b3b16bce4 inputs 2023-10-13 08:46:35 +01:00
Pavel
024674eef3 List check 2023-10-13 11:42:42 +04:00
Pavel
b7d88b4c0f fix wrong link 2023-10-12 19:45:36 +04:00
Pavel
719ca63ec1 fixes 2023-10-12 19:40:23 +04:00
Pavel
2cfb416fd0 Desc loader 2023-10-12 13:44:32 +04:00
Pavel
50f07f9ef5 limit crawler 2023-10-12 12:53:33 +04:00
Pavel
c517bdd2e1 Crawler + sitemap 2023-10-12 12:35:26 +04:00
Pavel
658867cb46 No crawler, no sitemap 2023-10-12 01:03:40 +04:00
Alex
8f2ad38503 tests 2023-10-11 10:13:51 +01:00
141 changed files with 23757 additions and 6956 deletions

View File

@@ -1,4 +1,5 @@
API_KEY=<LLM api key (for example, open ai key)>
LLM_NAME=docsgpt
VITE_API_STREAMING=true
#For Azure (you can delete it if you don't use Azure)

6
.github/holopin.yml vendored
View File

@@ -1,5 +1,5 @@
organization: arc53
defaultSticker: cln9dm7qz164460gk5ksrgr034
defaultSticker: clqmdf0ed34290glbvqh0kzxd
stickers:
- id: cln9dm7qz164460gk5ksrgr034
alias: hacktober
- id: clqmdf0ed34290glbvqh0kzxd
alias: festive

2
.gitignore vendored
View File

@@ -75,6 +75,7 @@ target/
# Jupyter Notebook
.ipynb_checkpoints
**/*.ipynb
# IPython
profile_default/
@@ -172,3 +173,4 @@ application/vectors/
node_modules/
.vscode/settings.json
models/
model/

View File

@@ -18,7 +18,7 @@ Say goodbye to time-consuming manual searches, and let <strong><a href="https://
<a href="https://github.com/arc53/DocsGPT">![link to main GitHub showing Forks number](https://img.shields.io/github/forks/arc53/docsgpt?style=social)</a>
<a href="https://github.com/arc53/DocsGPT/blob/main/LICENSE">![link to license file](https://img.shields.io/github/license/arc53/docsgpt)</a>
<a href="https://discord.gg/n5BX8dh8rU">![link to discord](https://img.shields.io/discord/1070046503302877216)</a>
<a href="https://twitter.com/ATushynski">![X (formerly Twitter) URL](https://img.shields.io/twitter/follow/ATushynski)</a>
<a href="https://twitter.com/docsgptai">![X (formerly Twitter) URL](https://img.shields.io/twitter/follow/docsgptai)</a>
</div>
@@ -40,7 +40,7 @@ You can find our roadmap [here](https://github.com/orgs/arc53/projects/2). Pleas
| Name | Base Model | Requirements (or similar) |
| --------------------------------------------------------------------- | ----------- | ------------------------- |
| [Docsgpt-7b-falcon](https://huggingface.co/Arc53/docsgpt-7b-falcon) | Falcon-7b | 1xA10G gpu |
| [Docsgpt-7b-mistral](https://huggingface.co/Arc53/docsgpt-7b-mistral) | Mistral-7b | 1xA10G gpu |
| [Docsgpt-14b](https://huggingface.co/Arc53/docsgpt-14b) | llama-2-14b | 2xA10 gpu's |
| [Docsgpt-40b-falcon](https://huggingface.co/Arc53/docsgpt-40b-falcon) | falcon-40b | 8xA10G gpu's |
@@ -83,17 +83,18 @@ On Mac OS or Linux, write:
`./setup.sh`
It will install all the dependencies and allow you to download the local model or use OpenAI.
It will install all the dependencies and allow you to download the local model, use OpenAI or use our LLM API.
Otherwise, refer to this Guide:
1. Download and open this repository with `git clone https://github.com/arc53/DocsGPT.git`
2. Create a `.env` file in your root directory and set the env variable `API_KEY` with your [OpenAI API key](https://platform.openai.com/account/api-keys) and `VITE_API_STREAMING` to true or false, depending on whether you want streaming answers or not.
2. Create a `.env` file in your root directory and set the env variables and `VITE_API_STREAMING` to true or false, depending on whether you want streaming answers or not.
It should look like this inside:
```
API_KEY=Yourkey
LLM_NAME=[docsgpt or openai or others]
VITE_API_STREAMING=true
API_KEY=[if LLM_NAME is openai]
```
See optional environment variables in the [/.env-template](https://github.com/arc53/DocsGPT/blob/main/.env-template) and [/application/.env_sample](https://github.com/arc53/DocsGPT/blob/main/application/.env_sample) files.
@@ -122,8 +123,8 @@ docker compose -f docker-compose-dev.yaml up -d
> [!Note]
> Make sure you have Python 3.10 or 3.11 installed.
1. Export required environment variables or prepare a `.env` file in the `/application` folder:
- Copy [.env_sample](https://github.com/arc53/DocsGPT/blob/main/application/.env_sample) and create `.env` with your OpenAI API token for the `API_KEY` and `EMBEDDINGS_KEY` fields.
1. Export required environment variables or prepare a `.env` file in the project folder:
- Copy [.env_sample](https://github.com/arc53/DocsGPT/blob/main/application/.env_sample) and create `.env`.
(check out [`application/core/settings.py`](application/core/settings.py) if you want to see more config options.)
@@ -144,14 +145,23 @@ python -m venv venv
venv/Scripts/activate
```
3. Change to the `application/` subdir by the command `cd application/` and install dependencies for the backend:
3. Download embedding model and save it in the `model/` folder:
You can use the script below, or download it manually from [here](https://d3dg1063dc54p9.cloudfront.net/models/embeddings/mpnet-base-v2.zip), unzip it and save it in the `model/` folder.
```commandline
wget https://d3dg1063dc54p9.cloudfront.net/models/embeddings/mpnet-base-v2.zip
unzip mpnet-base-v2.zip -d model
rm mpnet-base-v2.zip
```
4. Install dependencies for the backend:
```commandline
pip install -r application/requirements.txt
```
4. Run the app using `flask --app application/app.py run --host=0.0.0.0 --port=7091`.
5. Start worker with `celery -A application.app.celery worker -l INFO`.
5. Run the app using `flask --app application/app.py run --host=0.0.0.0 --port=7091`.
6. Start worker with `celery -A application.app.celery worker -l INFO`.
### Start Frontend

View File

@@ -1,19 +1,27 @@
FROM python:3.10-slim-bullseye as builder
FROM python:3.11-slim-bullseye as builder
# Tiktoken requires Rust toolchain, so build it in a separate stage
RUN apt-get update && apt-get install -y gcc curl
RUN apt-get install -y wget unzip
RUN wget https://d3dg1063dc54p9.cloudfront.net/models/embeddings/mpnet-base-v2.zip
RUN unzip mpnet-base-v2.zip -d model
RUN rm mpnet-base-v2.zip
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y && apt-get install --reinstall libc6-dev -y
ENV PATH="/root/.cargo/bin:${PATH}"
RUN pip install --upgrade pip && pip install tiktoken==0.3.3
RUN pip install --upgrade pip && pip install tiktoken==0.5.2
COPY requirements.txt .
RUN pip install -r requirements.txt
FROM python:3.10-slim-bullseye
FROM python:3.11-slim-bullseye
# Copy pre-built packages and binaries from builder stage
COPY --from=builder /usr/local/ /usr/local/
WORKDIR /app
COPY --from=builder /model /app/model
COPY . /app/application
ENV FLASK_APP=app.py
ENV FLASK_DEBUG=true

View File

@@ -26,14 +26,18 @@ db = mongo["docsgpt"]
conversations_collection = db["conversations"]
vectors_collection = db["vectors"]
prompts_collection = db["prompts"]
api_key_collection = db["api_keys"]
answer = Blueprint('answer', __name__)
if settings.LLM_NAME == "gpt4":
gpt_model = 'gpt-4'
gpt_model = ""
# to have some kind of default behaviour
if settings.LLM_NAME == "openai":
gpt_model = 'gpt-3.5-turbo'
elif settings.LLM_NAME == "anthropic":
gpt_model = 'claude-2'
else:
gpt_model = 'gpt-3.5-turbo'
if settings.MODEL_NAME: # in case there is particular model name configured
gpt_model = settings.MODEL_NAME
# load the prompts
current_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -74,6 +78,12 @@ def run_async_chain(chain, question, chat_history):
result["answer"] = answer
return result
def get_data_from_api_key(api_key):
data = api_key_collection.find_one({"key": api_key})
if data is None:
return bad_request(401, "Invalid API key")
return data
def get_vectorstore(data):
if "active_docs" in data:
@@ -95,9 +105,8 @@ def is_azure_configured():
return settings.OPENAI_API_BASE and settings.OPENAI_API_VERSION and settings.AZURE_DEPLOYMENT_NAME
def complete_stream(question, docsearch, chat_history, api_key, prompt_id, conversation_id):
llm = LLMCreator.create_llm(settings.LLM_NAME, api_key=api_key)
def complete_stream(question, docsearch, chat_history, prompt_id, conversation_id, chunks=2):
llm = LLMCreator.create_llm(settings.LLM_NAME, api_key=settings.API_KEY)
if prompt_id == 'default':
prompt = chat_combine_template
elif prompt_id == 'creative':
@@ -106,8 +115,11 @@ def complete_stream(question, docsearch, chat_history, api_key, prompt_id, conve
prompt = chat_combine_strict
else:
prompt = prompts_collection.find_one({"_id": ObjectId(prompt_id)})["content"]
docs = docsearch.search(question, k=2)
if chunks == 0:
docs = []
else:
docs = docsearch.search(question, k=chunks)
if settings.LLM_NAME == "llama.cpp":
docs = [docs[0]]
# join all page_content together with a newline
@@ -117,12 +129,9 @@ def complete_stream(question, docsearch, chat_history, api_key, prompt_id, conve
source_log_docs = []
for doc in docs:
if doc.metadata:
data = json.dumps({"type": "source", "doc": doc.page_content, "metadata": doc.metadata})
source_log_docs.append({"title": doc.metadata['title'].split('/')[-1], "text": doc.page_content})
else:
data = json.dumps({"type": "source", "doc": doc.page_content})
source_log_docs.append({"title": doc.page_content, "text": doc.page_content})
yield f"data:{data}\n\n"
if len(chat_history) > 1:
tokens_current_history = 0
@@ -185,36 +194,43 @@ def stream():
data = request.get_json()
# get parameter from url question
question = data["question"]
history = data["history"]
# history to json object from string
history = json.loads(history)
conversation_id = data["conversation_id"]
if "history" not in data:
history = []
else:
history = data["history"]
history = json.loads(history)
if "conversation_id" not in data:
conversation_id = None
else:
conversation_id = data["conversation_id"]
if 'prompt_id' in data:
prompt_id = data["prompt_id"]
else:
prompt_id = 'default'
if 'selectedDocs' in data and data['selectedDocs'] is None:
chunks = 0
elif 'chunks' in data:
chunks = int(data["chunks"])
else:
chunks = 2
# check if active_docs is set
if not api_key_set:
api_key = data["api_key"]
else:
api_key = settings.API_KEY
if not embeddings_key_set:
embeddings_key = data["embeddings_key"]
else:
embeddings_key = settings.EMBEDDINGS_KEY
if "active_docs" in data:
if "api_key" in data:
data_key = get_data_from_api_key(data["api_key"])
vectorstore = get_vectorstore({"active_docs": data_key["source"]})
elif "active_docs" in data:
vectorstore = get_vectorstore({"active_docs": data["active_docs"]})
else:
vectorstore = ""
docsearch = VectorCreator.create_vectorstore(settings.VECTOR_STORE, vectorstore, embeddings_key)
docsearch = VectorCreator.create_vectorstore(settings.VECTOR_STORE, vectorstore, settings.EMBEDDINGS_KEY)
return Response(
complete_stream(question, docsearch,
chat_history=history, api_key=api_key,
chat_history=history,
prompt_id=prompt_id,
conversation_id=conversation_id), mimetype="text/event-stream"
conversation_id=conversation_id,
chunks=chunks), mimetype="text/event-stream"
)
@@ -222,24 +238,23 @@ def stream():
def api_answer():
data = request.get_json()
question = data["question"]
history = data["history"]
if "history" not in data:
history = []
else:
history = data["history"]
if "conversation_id" not in data:
conversation_id = None
else:
conversation_id = data["conversation_id"]
print("-" * 5)
if not api_key_set:
api_key = data["api_key"]
else:
api_key = settings.API_KEY
if not embeddings_key_set:
embeddings_key = data["embeddings_key"]
else:
embeddings_key = settings.EMBEDDINGS_KEY
if 'prompt_id' in data:
prompt_id = data["prompt_id"]
else:
prompt_id = 'default'
if 'chunks' in data:
chunks = int(data["chunks"])
else:
chunks = 2
if prompt_id == 'default':
prompt = chat_combine_template
@@ -253,17 +268,24 @@ def api_answer():
# use try and except to check for exception
try:
# check if the vectorstore is set
vectorstore = get_vectorstore(data)
if "api_key" in data:
data_key = get_data_from_api_key(data["api_key"])
vectorstore = get_vectorstore({"active_docs": data_key["source"]})
else:
vectorstore = get_vectorstore(data)
# loading the index and the store and the prompt template
# Note if you have used other embeddings than OpenAI, you need to change the embeddings
docsearch = VectorCreator.create_vectorstore(settings.VECTOR_STORE, vectorstore, embeddings_key)
docsearch = VectorCreator.create_vectorstore(settings.VECTOR_STORE, vectorstore, settings.EMBEDDINGS_KEY)
llm = LLMCreator.create_llm(settings.LLM_NAME, api_key=api_key)
llm = LLMCreator.create_llm(settings.LLM_NAME, api_key=settings.API_KEY)
docs = docsearch.search(question, k=2)
if chunks == 0:
docs = []
else:
docs = docsearch.search(question, k=chunks)
# join all page_content together with a newline
docs_together = "\n".join([doc.page_content for doc in docs])
p_chat_combine = prompt.replace("{summaries}", docs_together)
@@ -343,3 +365,37 @@ def api_answer():
traceback.print_exc()
print(str(e))
return bad_request(500, str(e))
@answer.route("/api/search", methods=["POST"])
def api_search():
data = request.get_json()
# get parameter from url question
question = data["question"]
if "api_key" in data:
data_key = get_data_from_api_key(data["api_key"])
vectorstore = data_key["source"]
elif "active_docs" in data:
vectorstore = get_vectorstore({"active_docs": data["active_docs"]})
else:
vectorstore = ""
if 'chunks' in data:
chunks = int(data["chunks"])
else:
chunks = 2
docsearch = VectorCreator.create_vectorstore(settings.VECTOR_STORE, vectorstore, settings.EMBEDDINGS_KEY)
if chunks == 0:
docs = []
else:
docs = docsearch.search(question, k=chunks)
source_log_docs = []
for doc in docs:
if doc.metadata:
source_log_docs.append({"title": doc.metadata['title'].split('/')[-1], "text": doc.page_content})
else:
source_log_docs.append({"title": doc.page_content, "text": doc.page_content})
#yield f"data:{data}\n\n"
return source_log_docs

View File

@@ -1,11 +1,13 @@
import os
import uuid
from flask import Blueprint, request, jsonify
from urllib.parse import urlparse
import requests
from pymongo import MongoClient
from bson.objectid import ObjectId
from werkzeug.utils import secure_filename
from application.api.user.tasks import ingest
from application.api.user.tasks import ingest, ingest_remote
from application.core.settings import settings
from application.vectorstore.vector_creator import VectorCreator
@@ -16,6 +18,7 @@ conversations_collection = db["conversations"]
vectors_collection = db["vectors"]
prompts_collection = db["prompts"]
feedback_collection = db["feedback"]
api_key_collection = db["api_keys"]
user = Blueprint('user', __name__)
current_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -36,7 +39,7 @@ def delete_conversation():
@user.route("/api/get_conversations", methods=["get"])
def get_conversations():
# provides a list of conversations
conversations = conversations_collection.find().sort("date", -1)
conversations = conversations_collection.find().sort("date", -1).limit(30)
list_conversations = []
for conversation in conversations:
list_conversations.append({"id": str(conversation["_id"]), "name": conversation["name"]})
@@ -157,6 +160,32 @@ def upload_file():
return {"status": "ok", "task_id": task_id}
else:
return {"status": "error"}
@user.route("/api/remote", methods=["POST"])
def upload_remote():
"""Upload a remote source to get vectorized and indexed."""
if "user" not in request.form:
return {"status": "no user"}
user = secure_filename(request.form["user"])
if "source" not in request.form:
return {"status": "no source"}
source = secure_filename(request.form["source"])
if "name" not in request.form:
return {"status": "no name"}
job_name = secure_filename(request.form["name"])
# check if the post request has the file part
if "data" not in request.form:
print("No data")
return {"status": "no data"}
source_data = request.form["data"]
if source_data:
task = ingest_remote.delay(source_data=source_data, job_name=job_name, user=user, loader=source)
# task id
task_id = task.id
return {"status": "ok", "task_id": task_id}
else:
return {"status": "error"}
@user.route("/api/task_status", methods=["GET"])
def task_status():
@@ -219,25 +248,31 @@ def check_docs():
# split docs on / and take first part
if data["docs"].split("/")[0] == "local":
return {"status": "exists"}
vectorstore = "vectors/" + data["docs"]
vectorstore = "vectors/" + secure_filename(data["docs"])
base_path = "https://raw.githubusercontent.com/arc53/DocsHUB/main/"
if os.path.exists(vectorstore) or data["docs"] == "default":
return {"status": "exists"}
else:
r = requests.get(base_path + vectorstore + "index.faiss")
file_url = urlparse(base_path + vectorstore + "index.faiss")
if file_url.scheme in ['https'] and file_url.netloc == 'raw.githubusercontent.com' and file_url.path.startswith('/arc53/DocsHUB/main/'):
r = requests.get(file_url.geturl())
if r.status_code != 200:
return {"status": "null"}
if r.status_code != 200:
return {"status": "null"}
else:
if not os.path.exists(vectorstore):
os.makedirs(vectorstore)
with open(vectorstore + "index.faiss", "wb") as f:
f.write(r.content)
# download the store
r = requests.get(base_path + vectorstore + "index.pkl")
with open(vectorstore + "index.pkl", "wb") as f:
f.write(r.content)
else:
if not os.path.exists(vectorstore):
os.makedirs(vectorstore)
with open(vectorstore + "index.faiss", "wb") as f:
f.write(r.content)
# download the store
r = requests.get(base_path + vectorstore + "index.pkl")
with open(vectorstore + "index.pkl", "wb") as f:
f.write(r.content)
return {"status": "null"}
return {"status": "loaded"}
@@ -317,5 +352,52 @@ def update_prompt_name():
@user.route("/api/get_api_keys", methods=["GET"])
def get_api_keys():
user = "local"
keys = api_key_collection.find({"user": user})
list_keys = []
for key in keys:
list_keys.append({
"id": str(key["_id"]),
"name": key["name"],
"key": key["key"][:4] + "..." + key["key"][-4:],
"source": key["source"],
"prompt_id": key["prompt_id"],
"chunks": key["chunks"]
})
return jsonify(list_keys)
@user.route("/api/create_api_key", methods=["POST"])
def create_api_key():
data = request.get_json()
name = data["name"]
source = data["source"]
prompt_id = data["prompt_id"]
chunks = data["chunks"]
key = str(uuid.uuid4())
user = "local"
resp = api_key_collection.insert_one(
{
"name": name,
"key": key,
"source": source,
"user": user,
"prompt_id": prompt_id,
"chunks": chunks
}
)
new_id = str(resp.inserted_id)
return {"id": new_id, "key": key}
@user.route("/api/delete_api_key", methods=["POST"])
def delete_api_key():
data = request.get_json()
id = data["id"]
api_key_collection.delete_one(
{
"_id": ObjectId(id),
}
)
return {"status": "ok"}

View File

@@ -1,7 +1,12 @@
from application.worker import ingest_worker
from application.worker import ingest_worker, remote_worker
from application.celery import celery
@celery.task(bind=True)
def ingest(self, directory, formats, name_job, filename, user):
resp = ingest_worker(self, directory, formats, name_job, filename, user)
return resp
@celery.task(bind=True)
def ingest_remote(self, source_data, job_name, user, loader):
resp = remote_worker(self, source_data, job_name, user, loader)
return resp

View File

@@ -40,5 +40,5 @@ def after_request(response):
return response
if __name__ == "__main__":
app.run(debug=True, port=7091)
app.run(debug=settings.FLASK_DEBUG_MODE, port=7091)

View File

@@ -1,42 +1,65 @@
from pathlib import Path
from typing import Optional
import os
from pydantic import BaseSettings
from pydantic_settings import BaseSettings
current_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
class Settings(BaseSettings):
LLM_NAME: str = "openai"
EMBEDDINGS_NAME: str = "openai_text-embedding-ada-002"
LLM_NAME: str = "docsgpt"
MODEL_NAME: Optional[str] = None # when LLM_NAME is openai, MODEL_NAME can be e.g. gpt-4-turbo-preview or gpt-3.5-turbo
EMBEDDINGS_NAME: str = "huggingface_sentence-transformers/all-mpnet-base-v2"
CELERY_BROKER_URL: str = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND: str = "redis://localhost:6379/1"
MONGO_URI: str = "mongodb://localhost:27017/docsgpt"
MODEL_PATH: str = os.path.join(current_dir, "models/docsgpt-7b-f16.gguf")
TOKENS_MAX_HISTORY: int = 150
UPLOAD_FOLDER: str = "inputs"
VECTOR_STORE: str = "faiss" # "faiss" or "elasticsearch"
VECTOR_STORE: str = "faiss" # "faiss" or "elasticsearch" or "qdrant"
API_URL: str = "http://localhost:7091" # backend url for celery worker
API_KEY: str = None # LLM api key
EMBEDDINGS_KEY: str = None # api key for embeddings (if using openai, just copy API_KEY)
OPENAI_API_BASE: str = None # azure openai api base url
OPENAI_API_VERSION: str = None # azure openai api version
AZURE_DEPLOYMENT_NAME: str = None # azure deployment name for answering
AZURE_EMBEDDINGS_DEPLOYMENT_NAME: str = None # azure deployment name for embeddings
API_KEY: Optional[str] = None # LLM api key
EMBEDDINGS_KEY: Optional[str] = None # api key for embeddings (if using openai, just copy API_KEY)
OPENAI_API_BASE: Optional[str] = None # azure openai api base url
OPENAI_API_VERSION: Optional[str] = None # azure openai api version
AZURE_DEPLOYMENT_NAME: Optional[str] = None # azure deployment name for answering
AZURE_EMBEDDINGS_DEPLOYMENT_NAME: Optional[str] = None # azure deployment name for embeddings
# elasticsearch
ELASTIC_CLOUD_ID: str = None # cloud id for elasticsearch
ELASTIC_USERNAME: str = None # username for elasticsearch
ELASTIC_PASSWORD: str = None # password for elasticsearch
ELASTIC_URL: str = None # url for elasticsearch
ELASTIC_INDEX: str = "docsgpt" # index name for elasticsearch
ELASTIC_CLOUD_ID: Optional[str] = None # cloud id for elasticsearch
ELASTIC_USERNAME: Optional[str] = None # username for elasticsearch
ELASTIC_PASSWORD: Optional[str] = None # password for elasticsearch
ELASTIC_URL: Optional[str] = None # url for elasticsearch
ELASTIC_INDEX: Optional[str] = "docsgpt" # index name for elasticsearch
# SageMaker config
SAGEMAKER_ENDPOINT: str = None # SageMaker endpoint name
SAGEMAKER_REGION: str = None # SageMaker region name
SAGEMAKER_ACCESS_KEY: str = None # SageMaker access key
SAGEMAKER_SECRET_KEY: str = None # SageMaker secret key
SAGEMAKER_ENDPOINT: Optional[str] = None # SageMaker endpoint name
SAGEMAKER_REGION: Optional[str] = None # SageMaker region name
SAGEMAKER_ACCESS_KEY: Optional[str] = None # SageMaker access key
SAGEMAKER_SECRET_KEY: Optional[str] = None # SageMaker secret key
# prem ai project id
PREMAI_PROJECT_ID: Optional[str] = None
# Qdrant vectorstore config
QDRANT_COLLECTION_NAME: Optional[str] = "docsgpt"
QDRANT_LOCATION: Optional[str] = None
QDRANT_URL: Optional[str] = None
QDRANT_PORT: Optional[int] = 6333
QDRANT_GRPC_PORT: int = 6334
QDRANT_PREFER_GRPC: bool = False
QDRANT_HTTPS: Optional[bool] = None
QDRANT_API_KEY: Optional[str] = None
QDRANT_PREFIX: Optional[str] = None
QDRANT_TIMEOUT: Optional[float] = None
QDRANT_HOST: Optional[str] = None
QDRANT_PATH: Optional[str] = None
QDRANT_DISTANCE_FUNC: str = "Cosine"
FLASK_DEBUG_MODE: bool = False
path = Path(__file__).parent.parent.absolute()

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,49 @@
from application.llm.base import BaseLLM
import json
import requests
class DocsGPTAPILLM(BaseLLM):
def __init__(self, *args, **kwargs):
self.endpoint = "https://llm.docsgpt.co.uk"
def gen(self, model, engine, messages, stream=False, **kwargs):
context = messages[0]['content']
user_question = messages[-1]['content']
prompt = f"### Instruction \n {user_question} \n ### Context \n {context} \n ### Answer \n"
response = requests.post(
f"{self.endpoint}/answer",
json={
"prompt": prompt,
"max_new_tokens": 30
}
)
response_clean = response.json()['a'].replace("###", "")
return response_clean
def gen_stream(self, model, engine, messages, stream=True, **kwargs):
context = messages[0]['content']
user_question = messages[-1]['content']
prompt = f"### Instruction \n {user_question} \n ### Context \n {context} \n ### Answer \n"
# send prompt to endpoint /stream
response = requests.post(
f"{self.endpoint}/stream",
json={
"prompt": prompt,
"max_new_tokens": 256
},
stream=True
)
for line in response.iter_lines():
if line:
#data = json.loads(line)
data_str = line.decode('utf-8')
if data_str.startswith("data: "):
data = json.loads(data_str[6:])
yield data['a']

View File

@@ -3,6 +3,8 @@ from application.llm.sagemaker import SagemakerAPILLM
from application.llm.huggingface import HuggingFaceLLM
from application.llm.llama_cpp import LlamaCpp
from application.llm.anthropic import AnthropicLLM
from application.llm.docsgpt_provider import DocsGPTAPILLM
from application.llm.premai import PremAILLM
@@ -13,7 +15,9 @@ class LLMCreator:
'sagemaker': SagemakerAPILLM,
'huggingface': HuggingFaceLLM,
'llama.cpp': LlamaCpp,
'anthropic': AnthropicLLM
'anthropic': AnthropicLLM,
'docsgpt': DocsGPTAPILLM,
'premai': PremAILLM,
}
@classmethod

View File

@@ -5,40 +5,38 @@ class OpenAILLM(BaseLLM):
def __init__(self, api_key):
global openai
import openai
openai.api_key = api_key
self.api_key = api_key # Save the API key to be used later
from openai import OpenAI
self.client = OpenAI(
api_key=api_key,
)
self.api_key = api_key
def _get_openai(self):
# Import openai when needed
import openai
# Set the API key every time you import openai
openai.api_key = self.api_key
return openai
def gen(self, model, engine, messages, stream=False, **kwargs):
response = openai.ChatCompletion.create(
model=model,
engine=engine,
response = self.client.chat.completions.create(model=model,
messages=messages,
stream=stream,
**kwargs
)
**kwargs)
return response["choices"][0]["message"]["content"]
return response.choices[0].message.content
def gen_stream(self, model, engine, messages, stream=True, **kwargs):
response = openai.ChatCompletion.create(
model=model,
engine=engine,
response = self.client.chat.completions.create(model=model,
messages=messages,
stream=stream,
**kwargs
)
**kwargs)
for line in response:
if "content" in line["choices"][0]["delta"]:
yield line["choices"][0]["delta"]["content"]
# import sys
# print(line.choices[0].delta.content, file=sys.stderr)
if line.choices[0].delta.content is not None:
yield line.choices[0].delta.content
class AzureOpenAILLM(OpenAILLM):
@@ -48,10 +46,15 @@ class AzureOpenAILLM(OpenAILLM):
self.api_base = settings.OPENAI_API_BASE,
self.api_version = settings.OPENAI_API_VERSION,
self.deployment_name = settings.AZURE_DEPLOYMENT_NAME,
from openai import AzureOpenAI
self.client = AzureOpenAI(
api_key=openai_api_key,
api_version=settings.OPENAI_API_VERSION,
api_base=settings.OPENAI_API_BASE,
deployment_name=settings.AZURE_DEPLOYMENT_NAME,
)
def _get_openai(self):
openai = super()._get_openai()
openai.api_base = self.api_base
openai.api_version = self.api_version
openai.api_type = "azure"
return openai

33
application/llm/premai.py Normal file
View File

@@ -0,0 +1,33 @@
from application.llm.base import BaseLLM
from application.core.settings import settings
class PremAILLM(BaseLLM):
def __init__(self, api_key):
from premai import Prem
self.client = Prem(
api_key=api_key
)
self.api_key = api_key
self.project_id = settings.PREMAI_PROJECT_ID
def gen(self, model, engine, messages, stream=False, **kwargs):
response = self.client.chat.completions.create(model=model,
project_id=self.project_id,
messages=messages,
stream=stream,
**kwargs)
return response.choices[0].message["content"]
def gen_stream(self, model, engine, messages, stream=True, **kwargs):
response = self.client.chat.completions.create(model=model,
project_id=self.project_id,
messages=messages,
stream=stream,
**kwargs)
for line in response:
if line.choices[0].delta["content"] is not None:
yield line.choices[0].delta["content"]

View File

@@ -62,7 +62,6 @@ class SimpleDirectoryReader(BaseReader):
file_extractor: Optional[Dict[str, BaseParser]] = None,
num_files_limit: Optional[int] = None,
file_metadata: Optional[Callable[[str], Dict]] = None,
chunk_size_max: int = 2048,
) -> None:
"""Initialize with parameters."""
super().__init__()
@@ -148,12 +147,24 @@ class SimpleDirectoryReader(BaseReader):
# do standard read
with open(input_file, "r", errors=self.errors) as f:
data = f.read()
if isinstance(data, List):
data_list.extend(data)
else:
data_list.append(str(data))
# Prepare metadata for this file
if self.file_metadata is not None:
metadata_list.append(self.file_metadata(str(input_file)))
file_metadata = self.file_metadata(str(input_file))
else:
# Provide a default empty metadata
file_metadata = {'title': '', 'store': ''}
# TODO: Find a case with no metadata and check if breaks anything
if isinstance(data, List):
# Extend data_list with each item in the data list
data_list.extend([str(d) for d in data])
# For each item in the data list, add the file's metadata to metadata_list
metadata_list.extend([file_metadata for _ in data])
else:
# Add the single piece of data to data_list
data_list.append(str(data))
# Add the file's metadata to metadata_list
metadata_list.append(file_metadata)
if concatenate:
return [Document("\n".join(data_list))]

View File

@@ -6,9 +6,9 @@ from application.core.settings import settings
from retry import retry
# from langchain.embeddings import HuggingFaceEmbeddings
# from langchain.embeddings import HuggingFaceInstructEmbeddings
# from langchain.embeddings import CohereEmbeddings
# from langchain_community.embeddings import HuggingFaceEmbeddings
# from langchain_community.embeddings import HuggingFaceInstructEmbeddings
# from langchain_community.embeddings import CohereEmbeddings
def num_tokens_from_string(string: str, encoding_name: str) -> int:

View File

@@ -0,0 +1,19 @@
"""Base reader class."""
from abc import abstractmethod
from typing import Any, List
from langchain.docstore.document import Document as LCDocument
from application.parser.schema.base import Document
class BaseRemote:
"""Utilities for loading data from a directory."""
@abstractmethod
def load_data(self, *args: Any, **load_kwargs: Any) -> List[Document]:
"""Load data from the input directory."""
def load_langchain_documents(self, **load_kwargs: Any) -> List[LCDocument]:
"""Load data in LangChain document format."""
docs = self.load_data(**load_kwargs)
return [d.to_langchain_format() for d in docs]

View File

@@ -0,0 +1,59 @@
import requests
from urllib.parse import urlparse, urljoin
from bs4 import BeautifulSoup
from application.parser.remote.base import BaseRemote
class CrawlerLoader(BaseRemote):
def __init__(self, limit=10):
from langchain.document_loaders import WebBaseLoader
self.loader = WebBaseLoader # Initialize the document loader
self.limit = limit # Set the limit for the number of pages to scrape
def load_data(self, inputs):
url = inputs
# Check if the input is a list and if it is, use the first element
if isinstance(url, list) and url:
url = url[0]
# Check if the URL scheme is provided, if not, assume http
if not urlparse(url).scheme:
url = "http://" + url
visited_urls = set() # Keep track of URLs that have been visited
base_url = urlparse(url).scheme + "://" + urlparse(url).hostname # Extract the base URL
urls_to_visit = [url] # List of URLs to be visited, starting with the initial URL
loaded_content = [] # Store the loaded content from each URL
# Continue crawling until there are no more URLs to visit
while urls_to_visit:
current_url = urls_to_visit.pop(0) # Get the next URL to visit
visited_urls.add(current_url) # Mark the URL as visited
# Try to load and process the content from the current URL
try:
response = requests.get(current_url) # Fetch the content of the current URL
response.raise_for_status() # Raise an exception for HTTP errors
loader = self.loader([current_url]) # Initialize the document loader for the current URL
loaded_content.extend(loader.load()) # Load the content and add it to the loaded_content list
except Exception as e:
# Print an error message if loading or processing fails and continue with the next URL
print(f"Error processing URL {current_url}: {e}")
continue
# Parse the HTML content to extract all links
soup = BeautifulSoup(response.text, 'html.parser')
all_links = [
urljoin(current_url, a['href'])
for a in soup.find_all('a', href=True)
if base_url in urljoin(current_url, a['href']) # Ensure links are from the same domain
]
# Add new links to the list of URLs to visit if they haven't been visited yet
urls_to_visit.extend([link for link in all_links if link not in visited_urls])
urls_to_visit = list(set(urls_to_visit)) # Remove duplicate URLs
# Stop crawling if the limit of pages to scrape is reached
if self.limit is not None and len(visited_urls) >= self.limit:
break
return loaded_content # Return the loaded content from all visited URLs

View File

@@ -0,0 +1,26 @@
from application.parser.remote.base import BaseRemote
from langchain_community.document_loaders import RedditPostsLoader
class RedditPostsLoaderRemote(BaseRemote):
def load_data(self, inputs):
data = eval(inputs)
client_id = data.get("client_id")
client_secret = data.get("client_secret")
user_agent = data.get("user_agent")
categories = data.get("categories", ["new", "hot"])
mode = data.get("mode", "subreddit")
search_queries = data.get("search_queries")
number_posts = data.get("number_posts", 10)
self.loader = RedditPostsLoader(
client_id=client_id,
client_secret=client_secret,
user_agent=user_agent,
categories=categories,
mode=mode,
search_queries=search_queries,
number_posts=number_posts,
)
documents = self.loader.load()
print(f"Loaded {len(documents)} documents from Reddit")
return documents

View File

@@ -0,0 +1,20 @@
from application.parser.remote.sitemap_loader import SitemapLoader
from application.parser.remote.crawler_loader import CrawlerLoader
from application.parser.remote.web_loader import WebLoader
from application.parser.remote.reddit_loader import RedditPostsLoaderRemote
class RemoteCreator:
loaders = {
"url": WebLoader,
"sitemap": SitemapLoader,
"crawler": CrawlerLoader,
"reddit": RedditPostsLoaderRemote,
}
@classmethod
def create_loader(cls, type, *args, **kwargs):
loader_class = cls.loaders.get(type.lower())
if not loader_class:
raise ValueError(f"No LLM class found for type {type}")
return loader_class(*args, **kwargs)

View File

@@ -0,0 +1,81 @@
import requests
import re # Import regular expression library
import xml.etree.ElementTree as ET
from application.parser.remote.base import BaseRemote
class SitemapLoader(BaseRemote):
def __init__(self, limit=20):
from langchain.document_loaders import WebBaseLoader
self.loader = WebBaseLoader
self.limit = limit # Adding limit to control the number of URLs to process
def load_data(self, inputs):
sitemap_url= inputs
# Check if the input is a list and if it is, use the first element
if isinstance(sitemap_url, list) and sitemap_url:
url = sitemap_url[0]
urls = self._extract_urls(sitemap_url)
if not urls:
print(f"No URLs found in the sitemap: {sitemap_url}")
return []
# Load content of extracted URLs
documents = []
processed_urls = 0 # Counter for processed URLs
for url in urls:
if self.limit is not None and processed_urls >= self.limit:
break # Stop processing if the limit is reached
try:
loader = self.loader([url])
documents.extend(loader.load())
processed_urls += 1 # Increment the counter after processing each URL
except Exception as e:
print(f"Error processing URL {url}: {e}")
continue
return documents
def _extract_urls(self, sitemap_url):
try:
response = requests.get(sitemap_url)
response.raise_for_status() # Raise an exception for HTTP errors
except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError) as e:
print(f"Failed to fetch sitemap: {sitemap_url}. Error: {e}")
return []
# Determine if this is a sitemap or a URL
if self._is_sitemap(response):
# It's a sitemap, so parse it and extract URLs
return self._parse_sitemap(response.content)
else:
# It's not a sitemap, return the URL itself
return [sitemap_url]
def _is_sitemap(self, response):
content_type = response.headers.get('Content-Type', '')
if 'xml' in content_type or response.url.endswith('.xml'):
return True
if '<sitemapindex' in response.text or '<urlset' in response.text:
return True
return False
def _parse_sitemap(self, sitemap_content):
# Remove namespaces
sitemap_content = re.sub(' xmlns="[^"]+"', '', sitemap_content.decode('utf-8'), count=1)
root = ET.fromstring(sitemap_content)
urls = []
for loc in root.findall('.//url/loc'):
urls.append(loc.text)
# Check for nested sitemaps
for sitemap in root.findall('.//sitemap/loc'):
nested_sitemap_url = sitemap.text
urls.extend(self._extract_urls(nested_sitemap_url))
return urls

View File

@@ -0,0 +1,11 @@
from langchain.document_loader import TelegramChatApiLoader
from application.parser.remote.base import BaseRemote
class TelegramChatApiRemote(BaseRemote):
def _init_parser(self, *args, **load_kwargs):
self.loader = TelegramChatApiLoader(**load_kwargs)
return {}
def parse_file(self, *args, **load_kwargs):
return

View File

@@ -0,0 +1,22 @@
from application.parser.remote.base import BaseRemote
class WebLoader(BaseRemote):
def __init__(self):
from langchain.document_loaders import WebBaseLoader
self.loader = WebBaseLoader
def load_data(self, inputs):
urls = inputs
if isinstance(urls, str):
urls = [urls] # Convert string to list if a single URL is passed
documents = []
for url in urls:
try:
loader = self.loader([url]) # Process URLs one by one
documents.extend(loader.load())
except Exception as e:
print(f"Error processing URL {url}: {e}")
continue # Continue with the next URL if an error occurs
return documents

View File

@@ -21,16 +21,15 @@ def group_documents(documents: List[Document], min_tokens: int, max_tokens: int)
for doc in documents:
doc_len = len(tiktoken.get_encoding("cl100k_base").encode(doc.text))
if current_group is None:
current_group = Document(text=doc.text, doc_id=doc.doc_id, embedding=doc.embedding,
extra_info=doc.extra_info)
elif len(tiktoken.get_encoding("cl100k_base").encode(
current_group.text)) + doc_len < max_tokens and doc_len < min_tokens:
current_group.text += " " + doc.text
# Check if current group is empty or if the document can be added based on token count and matching metadata
if current_group is None or (len(tiktoken.get_encoding("cl100k_base").encode(current_group.text)) + doc_len < max_tokens and doc_len < min_tokens and current_group.extra_info == doc.extra_info):
if current_group is None:
current_group = doc # Use the document directly to retain its metadata
else:
current_group.text += " " + doc.text # Append text to the current group
else:
docs.append(current_group)
current_group = Document(text=doc.text, doc_id=doc.doc_id, embedding=doc.embedding,
extra_info=doc.extra_info)
current_group = doc # Start a new group with the current document
if current_group is not None:
docs.append(current_group)

View File

@@ -1,110 +1,34 @@
aiodns==3.0.0
aiohttp==3.8.6
aiohttp-retry==2.8.3
aiosignal==1.3.1
aleph-alpha-client==2.16.1
amqp==5.1.1
anthropic==0.5.0
async-timeout==4.0.2
attrs==22.2.0
billiard==3.6.4.0
blobfile==2.0.1
boto3==1.28.20
celery==5.2.7
cffi==1.15.1
charset-normalizer==3.1.0
click==8.1.3
click-didyoumean==0.3.0
click-plugins==1.1.1
click-repl==0.2.0
cryptography==41.0.4
dataclasses-json==0.5.7
decorator==5.1.1
anthropic==0.12.0
boto3==1.34.6
celery==5.3.6
dataclasses_json==0.6.3
docx2txt==0.8
dill==0.3.6
dnspython==2.3.0
ecdsa==0.18.0
elasticsearch==8.9.0
entrypoints==0.4
faiss-cpu==1.7.3
filelock==3.9.0
Flask==2.2.5
Flask-Cors==3.0.10
frozenlist==1.3.3
geojson==2.5.0
gunicorn==20.1.0
greenlet==2.0.2
gpt4all==0.1.7
huggingface-hub==0.15.1
humbug==0.3.2
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
jmespath==1.0.1
joblib==1.2.0
kombu==5.2.4
langchain==0.0.312
loguru==0.6.0
lxml==4.9.2
MarkupSafe==2.1.2
marshmallow==3.19.0
marshmallow-enum==1.5.1
mpmath==1.3.0
multidict==6.0.4
multiprocess==0.70.14
mypy-extensions==1.0.0
networkx==3.0
npx
EbookLib==0.18
elasticsearch==8.12.0
escodegen==1.0.11
esprima==4.0.1
faiss-cpu==1.7.4
Flask==3.0.1
gunicorn==21.2.0
html2text==2020.1.16
javalang==0.13.0
langchain==0.1.4
langchain-openai==0.0.5
nltk==3.8.1
numcodecs==0.11.0
numpy==1.24.2
openai==0.27.8
openapi3-parser==1.1.14
packaging==23.0
pathos==0.3.0
Pillow==10.0.1
pox==0.3.2
ppft==1.7.6.6
prompt-toolkit==3.0.38
py==1.11.0
pyasn1==0.4.8
pycares==4.3.0
pycparser==2.21
pycryptodomex==3.17
pycryptodome==3.19.0
pydantic==1.10.5
PyJWT==2.6.0
pymongo==4.3.3
pyowm==3.3.0
openapi3_parser==1.1.16
pandas==2.2.0
pydantic_settings==2.1.0
pymongo==4.6.1
PyPDF2==3.0.1
PySocks==1.7.1
pytest
python-dateutil==2.8.2
python-dotenv==1.0.0
python-jose==3.3.0
pytz==2022.7.1
PyYAML==6.0
redis==4.5.4
regex==2022.10.31
requests==2.31.0
python-dotenv==1.0.1
qdrant-client==1.7.3
redis==5.0.1
Requests==2.31.0
retry==0.9.2
rsa==4.9
scikit-learn==1.2.2
scipy==1.10.1
sentencepiece
six==1.16.0
SQLAlchemy==1.4.46
sympy==1.11.1
tenacity==8.2.2
threadpoolctl==3.1.0
tiktoken
tqdm==4.65.0
transformers==4.30.0
typer==0.7.0
typing-inspect==0.8.0
typing_extensions==4.5.0
urllib3==1.26.18
vine==5.0.0
wcwidth==0.2.6
yarl==1.8.2
sentence-transformers==2.2.2
sentence-transformers
tiktoken==0.5.2
torch==2.1.2
tqdm==4.66.1
transformers==4.36.2
unstructured==0.12.2
Werkzeug==3.0.1

View File

@@ -1,11 +1,11 @@
from abc import ABC, abstractmethod
import os
from langchain.embeddings import (
OpenAIEmbeddings,
from langchain_community.embeddings import (
HuggingFaceEmbeddings,
CohereEmbeddings,
HuggingFaceInstructEmbeddings,
)
from langchain_openai import OpenAIEmbeddings
from application.core.settings import settings
class BaseVectorStore(ABC):
@@ -44,6 +44,11 @@ class BaseVectorStore(ABC):
embedding_instance = embeddings_factory[embeddings_name](
cohere_api_key=embeddings_key
)
elif embeddings_name == "huggingface_sentence-transformers/all-mpnet-base-v2":
embedding_instance = embeddings_factory[embeddings_name](
#model_name="./model/all-mpnet-base-v2",
model_kwargs={"device": "cpu"},
)
else:
embedding_instance = embeddings_factory[embeddings_name]()

View File

@@ -0,0 +1,8 @@
class Document(str):
"""Class for storing a piece of text and associated metadata."""
def __new__(cls, page_content: str, metadata: dict):
instance = super().__new__(cls, page_content)
instance.page_content = page_content
instance.metadata = metadata
return instance

View File

@@ -1,16 +1,8 @@
from application.vectorstore.base import BaseVectorStore
from application.core.settings import settings
from application.vectorstore.document_class import Document
import elasticsearch
class Document(str):
"""Class for storing a piece of text and associated metadata."""
def __new__(cls, page_content: str, metadata: dict):
instance = super().__new__(cls, page_content)
instance.page_content = page_content
instance.metadata = metadata
return instance

View File

@@ -1,4 +1,4 @@
from langchain.vectorstores import FAISS
from langchain_community.vectorstores import FAISS
from application.vectorstore.base import BaseVectorStore
from application.core.settings import settings

View File

@@ -0,0 +1,126 @@
from application.vectorstore.base import BaseVectorStore
from application.core.settings import settings
from application.vectorstore.document_class import Document
class MongoDBVectorStore(BaseVectorStore):
def __init__(
self,
path: str = "",
embeddings_key: str = "embeddings",
collection: str = "documents",
index_name: str = "vector_search_index",
text_key: str = "text",
embedding_key: str = "embedding",
database: str = "docsgpt",
):
self._index_name = index_name
self._text_key = text_key
self._embedding_key = embedding_key
self._embeddings_key = embeddings_key
self._mongo_uri = settings.MONGO_URI
self._path = path.replace("application/indexes/", "").rstrip("/")
self._embedding = self._get_embeddings(settings.EMBEDDINGS_NAME, embeddings_key)
try:
import pymongo
except ImportError:
raise ImportError(
"Could not import pymongo python package. "
"Please install it with `pip install pymongo`."
)
self._client = pymongo.MongoClient(self._mongo_uri)
self._database = self._client[database]
self._collection = self._database[collection]
def search(self, question, k=2, *args, **kwargs):
query_vector = self._embedding.embed_query(question)
pipeline = [
{
"$vectorSearch": {
"queryVector": query_vector,
"path": self._embedding_key,
"limit": k,
"numCandidates": k * 10,
"index": self._index_name,
"filter": {
"store": {"$eq": self._path}
}
}
}
]
cursor = self._collection.aggregate(pipeline)
results = []
for doc in cursor:
text = doc[self._text_key]
doc.pop("_id")
doc.pop(self._text_key)
doc.pop(self._embedding_key)
metadata = doc
results.append(Document(text, metadata))
return results
def _insert_texts(self, texts, metadatas):
if not texts:
return []
embeddings = self._embedding.embed_documents(texts)
to_insert = [
{self._text_key: t, self._embedding_key: embedding, **m}
for t, m, embedding in zip(texts, metadatas, embeddings)
]
# insert the documents in MongoDB Atlas
insert_result = self._collection.insert_many(to_insert)
return insert_result.inserted_ids
def add_texts(self,
texts,
metadatas = None,
ids = None,
refresh_indices = True,
create_index_if_not_exists = True,
bulk_kwargs = None,
**kwargs,):
#dims = self._embedding.client[1].word_embedding_dimension
# # check if index exists
# if create_index_if_not_exists:
# # check if index exists
# info = self._collection.index_information()
# if self._index_name not in info:
# index_mongo = {
# "fields": [{
# "type": "vector",
# "path": self._embedding_key,
# "numDimensions": dims,
# "similarity": "cosine",
# },
# {
# "type": "filter",
# "path": "store"
# }]
# }
# self._collection.create_index(self._index_name, index_mongo)
batch_size = 100
_metadatas = metadatas or ({} for _ in texts)
texts_batch = []
metadatas_batch = []
result_ids = []
for i, (text, metadata) in enumerate(zip(texts, _metadatas)):
texts_batch.append(text)
metadatas_batch.append(metadata)
if (i + 1) % batch_size == 0:
result_ids.extend(self._insert_texts(texts_batch, metadatas_batch))
texts_batch = []
metadatas_batch = []
if texts_batch:
result_ids.extend(self._insert_texts(texts_batch, metadatas_batch))
return result_ids
def delete_index(self, *args, **kwargs):
self._collection.delete_many({"store": self._path})

View File

@@ -0,0 +1,47 @@
from langchain_community.vectorstores.qdrant import Qdrant
from application.vectorstore.base import BaseVectorStore
from application.core.settings import settings
from qdrant_client import models
class QdrantStore(BaseVectorStore):
def __init__(self, path: str = "", embeddings_key: str = "embeddings"):
self._filter = models.Filter(
must=[
models.FieldCondition(
key="metadata.store",
match=models.MatchValue(value=path.replace("application/indexes/", "").rstrip("/")),
)
]
)
self._docsearch = Qdrant.construct_instance(
["TEXT_TO_OBTAIN_EMBEDDINGS_DIMENSION"],
embedding=self._get_embeddings(settings.EMBEDDINGS_NAME, embeddings_key),
collection_name=settings.QDRANT_COLLECTION_NAME,
location=settings.QDRANT_LOCATION,
url=settings.QDRANT_URL,
port=settings.QDRANT_PORT,
grpc_port=settings.QDRANT_GRPC_PORT,
https=settings.QDRANT_HTTPS,
prefer_grpc=settings.QDRANT_PREFER_GRPC,
api_key=settings.QDRANT_API_KEY,
prefix=settings.QDRANT_PREFIX,
timeout=settings.QDRANT_TIMEOUT,
path=settings.QDRANT_PATH,
distance_func=settings.QDRANT_DISTANCE_FUNC,
)
def search(self, *args, **kwargs):
return self._docsearch.similarity_search(filter=self._filter, *args, **kwargs)
def add_texts(self, *args, **kwargs):
return self._docsearch.add_texts(*args, **kwargs)
def save_local(self, *args, **kwargs):
pass
def delete_index(self, *args, **kwargs):
return self._docsearch.client.delete(
collection_name=settings.QDRANT_COLLECTION_NAME, points_selector=self._filter
)

View File

@@ -1,11 +1,15 @@
from application.vectorstore.faiss import FaissStore
from application.vectorstore.elasticsearch import ElasticsearchStore
from application.vectorstore.mongodb import MongoDBVectorStore
from application.vectorstore.qdrant import QdrantStore
class VectorCreator:
vectorstores = {
'faiss': FaissStore,
'elasticsearch':ElasticsearchStore
"faiss": FaissStore,
"elasticsearch": ElasticsearchStore,
"mongodb": MongoDBVectorStore,
"qdrant": QdrantStore,
}
@classmethod
@@ -13,4 +17,4 @@ class VectorCreator:
vectorstore_class = cls.vectorstores.get(type.lower())
if not vectorstore_class:
raise ValueError(f"No vectorstore class found for type {type}")
return vectorstore_class(*args, **kwargs)
return vectorstore_class(*args, **kwargs)

View File

@@ -9,28 +9,33 @@ import requests
from application.core.settings import settings
from application.parser.file.bulk import SimpleDirectoryReader
from application.parser.remote.remote_creator import RemoteCreator
from application.parser.open_ai_func import call_openai_api
from application.parser.schema.base import Document
from application.parser.token_func import group_split
try:
nltk.download('punkt', quiet=True)
nltk.download('averaged_perceptron_tagger', quiet=True)
nltk.download("punkt", quiet=True)
nltk.download("averaged_perceptron_tagger", quiet=True)
except FileExistsError:
pass
# Define a function to extract metadata from a given filename.
def metadata_from_filename(title):
store = '/'.join(title.split('/')[1:3])
return {'title': title, 'store': store}
store = "/".join(title.split("/")[1:3])
return {"title": title, "store": store}
# Define a function to generate a random string of a given length.
def generate_random_string(length):
return ''.join([string.ascii_letters[i % 52] for i in range(length)])
return "".join([string.ascii_letters[i % 52] for i in range(length)])
current_dir = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
current_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Define the main function for ingesting and processing documents.
def ingest_worker(self, directory, formats, name_job, filename, user):
@@ -61,38 +66,52 @@ def ingest_worker(self, directory, formats, name_job, filename, user):
token_check = True
min_tokens = 150
max_tokens = 1250
full_path = directory + '/' + user + '/' + name_job
full_path = directory + "/" + user + "/" + name_job
import sys
print(full_path, file=sys.stderr)
# check if API_URL env variable is set
file_data = {'name': name_job, 'file': filename, 'user': user}
response = requests.get(urljoin(settings.API_URL, "/api/download"), params=file_data)
file_data = {"name": name_job, "file": filename, "user": user}
response = requests.get(
urljoin(settings.API_URL, "/api/download"), params=file_data
)
# check if file is in the response
print(response, file=sys.stderr)
file = response.content
if not os.path.exists(full_path):
os.makedirs(full_path)
with open(full_path + '/' + filename, 'wb') as f:
with open(full_path + "/" + filename, "wb") as f:
f.write(file)
# check if file is .zip and extract it
if filename.endswith('.zip'):
with zipfile.ZipFile(full_path + '/' + filename, 'r') as zip_ref:
if filename.endswith(".zip"):
with zipfile.ZipFile(full_path + "/" + filename, "r") as zip_ref:
zip_ref.extractall(full_path)
os.remove(full_path + '/' + filename)
os.remove(full_path + "/" + filename)
self.update_state(state='PROGRESS', meta={'current': 1})
self.update_state(state="PROGRESS", meta={"current": 1})
raw_docs = SimpleDirectoryReader(input_dir=full_path, input_files=input_files, recursive=recursive,
required_exts=formats, num_files_limit=limit,
exclude_hidden=exclude, file_metadata=metadata_from_filename).load_data()
raw_docs = group_split(documents=raw_docs, min_tokens=min_tokens, max_tokens=max_tokens, token_check=token_check)
raw_docs = SimpleDirectoryReader(
input_dir=full_path,
input_files=input_files,
recursive=recursive,
required_exts=formats,
num_files_limit=limit,
exclude_hidden=exclude,
file_metadata=metadata_from_filename,
).load_data()
raw_docs = group_split(
documents=raw_docs,
min_tokens=min_tokens,
max_tokens=max_tokens,
token_check=token_check,
)
docs = [Document.to_langchain_format(raw_doc) for raw_doc in raw_docs]
call_openai_api(docs, full_path, self)
self.update_state(state='PROGRESS', meta={'current': 100})
self.update_state(state="PROGRESS", meta={"current": 100})
if sample:
for i in range(min(5, len(raw_docs))):
@@ -100,24 +119,80 @@ def ingest_worker(self, directory, formats, name_job, filename, user):
# get files from outputs/inputs/index.faiss and outputs/inputs/index.pkl
# and send them to the server (provide user and name in form)
file_data = {'name': name_job, 'user': user}
file_data = {"name": name_job, "user": user}
if settings.VECTOR_STORE == "faiss":
files = {'file_faiss': open(full_path + '/index.faiss', 'rb'),
'file_pkl': open(full_path + '/index.pkl', 'rb')}
response = requests.post(urljoin(settings.API_URL, "/api/upload_index"), files=files, data=file_data)
response = requests.get(urljoin(settings.API_URL, "/api/delete_old?path=" + full_path))
files = {
"file_faiss": open(full_path + "/index.faiss", "rb"),
"file_pkl": open(full_path + "/index.pkl", "rb"),
}
response = requests.post(
urljoin(settings.API_URL, "/api/upload_index"), files=files, data=file_data
)
response = requests.get(
urljoin(settings.API_URL, "/api/delete_old?path=" + full_path)
)
else:
response = requests.post(urljoin(settings.API_URL, "/api/upload_index"), data=file_data)
response = requests.post(
urljoin(settings.API_URL, "/api/upload_index"), data=file_data
)
# delete local
shutil.rmtree(full_path)
return {
'directory': directory,
'formats': formats,
'name_job': name_job,
'filename': filename,
'user': user,
'limited': False
"directory": directory,
"formats": formats,
"name_job": name_job,
"filename": filename,
"user": user,
"limited": False,
}
def remote_worker(self, source_data, name_job, user, loader, directory="temp"):
# sample = False
token_check = True
min_tokens = 150
max_tokens = 1250
full_path = directory + "/" + user + "/" + name_job
if not os.path.exists(full_path):
os.makedirs(full_path)
self.update_state(state="PROGRESS", meta={"current": 1})
# source_data {"data": [url]} for url type task just urls
# Use RemoteCreator to load data from URL
remote_loader = RemoteCreator.create_loader(loader)
raw_docs = remote_loader.load_data(source_data)
docs = group_split(
documents=raw_docs,
min_tokens=min_tokens,
max_tokens=max_tokens,
token_check=token_check,
)
# docs = [Document.to_langchain_format(raw_doc) for raw_doc in raw_docs]
call_openai_api(docs, full_path, self)
self.update_state(state="PROGRESS", meta={"current": 100})
# Proceed with uploading and cleaning as in the original function
file_data = {"name": name_job, "user": user}
if settings.VECTOR_STORE == "faiss":
files = {
"file_faiss": open(full_path + "/index.faiss", "rb"),
"file_pkl": open(full_path + "/index.pkl", "rb"),
}
requests.post(
urljoin(settings.API_URL, "/api/upload_index"), files=files, data=file_data
)
requests.get(urljoin(settings.API_URL, "/api/delete_old?path=" + full_path))
else:
requests.post(urljoin(settings.API_URL, "/api/upload_index"), data=file_data)
shutil.rmtree(full_path)
return {"urls": source_data, "name_job": name_job, "user": user, "limited": False}

View File

@@ -1,4 +1,5 @@
from application.app import app
from application.core.settings import settings
if __name__ == "__main__":
app.run(debug=True, port=7091)
app.run(debug=settings.FLASK_DEBUG_MODE, port=7091)

View File

@@ -1,9 +1,9 @@
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.jsx'
})
theme: 'nextra-theme-docs',
themeConfig: './theme.config.jsx'
})
module.exports = withNextra()
module.exports = withNextra()
// If you have other Next.js configurations, you can pass them as the parameter:
// module.exports = withNextra({ /* other next.js config */ })
// If you have other Next.js configurations, you can pass them as the parameter:
// module.exports = withNextra({ /* other next.js config */ })

9390
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,17 @@
{
"scripts":{
"dev": "next dev",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"license": "MIT",
"license": "MIT",
"dependencies": {
"@vercel/analytics": "^1.0.2",
"docsgpt": "^0.2.4",
"next": "^13.4.19",
"nextra": "^2.12.3",
"nextra-theme-docs": "^2.12.3",
"@vercel/analytics": "^1.1.1",
"docsgpt": "^0.3.7",
"next": "^14.0.4",
"nextra": "^2.13.2",
"nextra-theme-docs": "^2.13.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
}

View File

@@ -107,3 +107,4 @@ Your instance is now available at your Public IP Address on port 5173. Enjoy usi
- [Deploy DocsGPT on Civo Compute Cloud](https://dev.to/rutamhere/deploying-docsgpt-on-civo-compute-c)
- [Deploy DocsGPT on DigitalOcean Droplet](https://dev.to/rutamhere/deploying-docsgpt-on-digitalocean-droplet-50ea)
- [Deploy DocsGPT on Kamatera Performance Cloud](https://dev.to/rutamhere/deploying-docsgpt-on-kamatera-performance-cloud-1bj)

View File

@@ -8,7 +8,7 @@ Just run the following command:
./setup.sh
```
This command will install all the necessary dependencies and provide you with an option to download the local model or use OpenAI.
This command will install all the necessary dependencies and provide you with an option to use our LLM API, download the local model or use OpenAI.
If you prefer to follow manual steps, refer to this guide:
@@ -16,7 +16,7 @@ If you prefer to follow manual steps, refer to this guide:
```bash
git clone https://github.com/arc53/DocsGPT.git
```
2. Create a `.env` file in your root directory and set your `API_KEY` with your [OpenAI API key](https://platform.openai.com/account/api-keys).
2. Create a `.env` file in your root directory and set your `API_KEY` with your [OpenAI API key](https://platform.openai.com/account/api-keys). (optional in case you want to use OpenAI)
3. Run the following commands:
```bash
docker-compose build && docker-compose up

View File

@@ -227,3 +227,124 @@ JSON response indicating the status of the operation:
```json
{ "status": "ok" }
```
### 7. /api/get_api_keys
**Description:**
The endpoint retrieves a list of API keys for the user.
**Request:**
**Method**: `GET`
**Sample JavaScript Fetch Request:**
```js
// get_api_keys (GET http://127.0.0.1:5000/api/get_api_keys)
fetch("http://localhost:5001/api/get_api_keys", {
"method": "GET",
"headers": {
"Content-Type": "application/json; charset=utf-8"
},
})
.then((res) => res.text())
.then(console.log.bind(console))
```
**Response:**
JSON response with a list of created API keys:
```json
[
{
"id": "string",
"name": "string",
"key": "string",
"source": "string"
},
...
]
```
### 8. /api/create_api_key
**Description:**
Create a new API key for the user.
**Request:**
**Method**: `POST`
**Headers**: Content-Type should be set to `application/json; charset=utf-8`
**Request Body**: JSON object with the following fields:
* `name` — A name for the API key.
* `source` — The source documents that will be used.
* `prompt_id` — The prompt ID.
* `chunks` — The number of chunks used to process an answer.
Here is a JavaScript Fetch Request example:
```js
// create_api_key (POST http://127.0.0.1:5000/api/create_api_key)
fetch("http://127.0.0.1:5000/api/create_api_key", {
"method": "POST",
"headers": {
"Content-Type": "application/json; charset=utf-8"
},
"body": JSON.stringify({"name":"Example Key Name",
"source":"Example Source",
"prompt_id":"creative",
"chunks":"2"})
})
.then((res) => res.json())
.then(console.log.bind(console))
```
**Response**
In response, you will get a JSON document containing the `id` and `key`:
```json
{
"id": "string",
"key": "string"
}
```
### 9. /api/delete_api_key
**Description:**
Delete an API key for the user.
**Request:**
**Method**: `POST`
**Headers**: Content-Type should be set to `application/json; charset=utf-8`
**Request Body**: JSON object with the field:
* `id` — The unique identifier of the API key to be deleted.
Here is a JavaScript Fetch Request example:
```js
// delete_api_key (POST http://127.0.0.1:5000/api/delete_api_key)
fetch("http://127.0.0.1:5000/api/delete_api_key", {
"method": "POST",
"headers": {
"Content-Type": "application/json; charset=utf-8"
},
"body": JSON.stringify({"id":"API_KEY_ID"})
})
.then((res) => res.json())
.then(console.log.bind(console))
```
**Response:**
In response, you will get a JSON document indicating the status of the operation:
```json
{
"status": "ok"
}
```

View File

@@ -4,7 +4,11 @@
"href": "/Extensions/Chatwoot-extension"
},
"react-widget": {
"title": "🏗️ Widget setup",
"href": "/Extensions/react-widget"
}
"title": "🏗️ Widget setup",
"href": "/Extensions/react-widget"
},
"api-key-guide": {
"title": "🔐 API Keys guide",
"href": "/Extensions/api-key-guide"
}
}

View File

@@ -0,0 +1,30 @@
## Guide to DocsGPT API Keys
DocsGPT API keys are essential for developers and users who wish to integrate the DocsGPT models into external applications, such as the our widget. This guide will walk you through the steps of obtaining an API key, starting from uploading your document to understanding the key variables associated with API keys.
### Uploading Your Document
Before creating your first API key, you must upload the document that will be linked to this key. You can upload your document through two methods:
- **GUI Web App Upload:** A user-friendly graphical interface that allows for easy upload and management of documents.
- **Using `/api/upload` Method:** For users comfortable with API calls, this method provides a direct way to upload documents.
### Obtaining Your API Key
After uploading your document, you can obtain an API key either through the graphical user interface or via an API call:
- **Graphical User Interface:** Navigate to the Settings section of the DocsGPT web app, find the API Keys option, and press 'Create New' to generate your key.
- **API Call:** Alternatively, you can use the `/api/create_api_key` endpoint to create a new API key. For detailed instructions, visit [DocsGPT API Documentation](https://docs.docsgpt.co.uk/Developing/API-docs#8-apicreate_api_key).
### Understanding Key Variables
Upon creating your API key, you will encounter several key variables. Each serves a specific purpose:
- **Name:** Assign a name to your API key for easy identification.
- **Source:** Indicates the source document(s) linked to your API key, which DocsGPT will use to generate responses.
- **ID:** A unique identifier for your API key. You can view this by making a call to `/api/get_api_keys`.
- **Key:** The API key itself, which will be used in your application to authenticate API requests.
With your API key ready, you can now integrate DocsGPT into your application, such as the DocsGPT Widget or any other software, via `/api/answer` or `/stream` endpoints. The source document is preset with the API key, allowing you to bypass fields like `selectDocs` and `active_docs` during implementation.
Congratulations on taking the first step towards enhancing your applications with DocsGPT! With this guide, you're now equipped to navigate the process of obtaining and understanding DocsGPT API keys.

View File

@@ -10,7 +10,6 @@ First, make sure you have Node.js and npm installed in your project. Then go to
In the file where you want to use the widget, import it and include the CSS file:
```js
import { DocsGPTWidget } from "docsgpt";
import "docsgpt/dist/style.css";
```
@@ -20,18 +19,28 @@ Now, you can use the widget in your component like this :
apiHost="https://your-docsgpt-api.com"
selectDocs="local/docs.zip"
apiKey=""
avatar = "https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png",
title = "Get AI assistance",
description = "DocsGPT's AI Chatbot is here to help",
heroTitle = "Welcome to DocsGPT !",
heroDescription="This chatbot is built with DocsGPT and utilises GenAI,
please review important information using sources."
/>
```
DocsGPTWidget takes 3 **props**:
DocsGPTWidget takes 8 **props** with default fallback values:
1. `apiHost` — The URL of your DocsGPT API.
2. `selectDocs` — The documentation source that you want to use for your widget (e.g. `default` or `local/docs1.zip`).
3. `apiKey` — Usually, it's empty.
4. `avatar`: Specifies the URL of the avatar or image representing the chatbot.
5. `title`: Sets the title text displayed in the chatbot interface.
6. `description`: Provides a brief description of the chatbot's purpose or functionality.
7. `heroTitle`: Displays a welcome title when users interact with the chatbot.
8. `heroDescription`: Provide additional introductory text or information about the chatbot's capabilities.
### How to use DocsGPTWidget with [Nextra](https://nextra.site/) (Next.js + MDX)
Install your widget as described above and then go to your `pages/` folder and create a new file `_app.js` with the following content:
```js
import { DocsGPTWidget } from "docsgpt";
import "docsgpt/dist/style.css";
export default function MyApp({ Component, pageProps }) {
return (

View File

@@ -1,11 +1,10 @@
import { DocsGPTWidget } from "docsgpt";
import "docsgpt/dist/style.css";
export default function MyApp({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
<DocsGPTWidget selectDocs="local/docsgpt-sep.zip/"/>
<DocsGPTWidget apiKey="d61a020c-ac8f-4f23-bb98-458e4da3c240" />
</>
)
}

3
extensions/react-widget/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
dist
.parcel-cache

View File

@@ -0,0 +1,10 @@
{
"extends": "@parcel/config-default",
"resolvers": ["@parcel/resolver-glob","..."],
"transformers": {
"*.svg": ["...", "@parcel/transformer-svg-react", "@parcel/transformer-typescript-tsc"]
},
"validators": {
"*.{ts,tsx}": ["@parcel/validator-typescript"]
}
}

View File

@@ -13,7 +13,6 @@ npm install docsgpt
```javascript
import { DocsGPTWidget } from "docsgpt";
import "docsgpt/dist/style.css";
const App = () => {
return <DocsGPTWidget />;
@@ -24,10 +23,18 @@ To link the widget to your api and your documents you can pass parameters to the
```javascript
import { DocsGPTWidget } from "docsgpt";
import "docsgpt/dist/style.css";
const App = () => {
return <DocsGPTWidget apiHost="http://localhost:7001" selectDocs='default' apiKey=''/>;
return <DocsGPTWidget
apiHost = 'http://localhost:7001',
selectDocs = 'default',
apiKey = '',
avatar = 'https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png',
title = 'Get AI assistance',
description = 'DocsGPT\'s AI Chatbot is here to help',
heroTitle = 'Welcome to DocsGPT !',
heroDescription='This chatbot is built with DocsGPT and utilises GenAI, please review important information using sources.'
/>;
};
```

9
extensions/react-widget/custom.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
declare module "*.svg" {
import * as React from "react";
const ReactComponent: React.FunctionComponent<
React.SVGProps<SVGSVGElement> & { title?: string }
>;
export default ReactComponent;
}

View File

@@ -1 +0,0 @@
export { DocsGPTWidget } from "./src/components/DocsGPTWidget";

View File

@@ -1,832 +0,0 @@
import Ne, { useState as ke, useRef as ur, useEffect as Pe } from "react";
var ne = { exports: {} }, Y = {};
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var Ce;
function cr() {
if (Ce)
return Y;
Ce = 1;
var N = Ne, w = Symbol.for("react.element"), C = Symbol.for("react.fragment"), u = Object.prototype.hasOwnProperty, E = N.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, S = { key: !0, ref: !0, __self: !0, __source: !0 };
function T(b, d, v) {
var m, h = {}, x = null, p = null;
v !== void 0 && (x = "" + v), d.key !== void 0 && (x = "" + d.key), d.ref !== void 0 && (p = d.ref);
for (m in d)
u.call(d, m) && !S.hasOwnProperty(m) && (h[m] = d[m]);
if (b && b.defaultProps)
for (m in d = b.defaultProps, d)
h[m] === void 0 && (h[m] = d[m]);
return { $$typeof: w, type: b, key: x, ref: p, props: h, _owner: E.current };
}
return Y.Fragment = C, Y.jsx = T, Y.jsxs = T, Y;
}
var L = {};
/**
* @license React
* react-jsx-runtime.development.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var Oe;
function fr() {
return Oe || (Oe = 1, process.env.NODE_ENV !== "production" && function() {
var N = Ne, w = Symbol.for("react.element"), C = Symbol.for("react.portal"), u = Symbol.for("react.fragment"), E = Symbol.for("react.strict_mode"), S = Symbol.for("react.profiler"), T = Symbol.for("react.provider"), b = Symbol.for("react.context"), d = Symbol.for("react.forward_ref"), v = Symbol.for("react.suspense"), m = Symbol.for("react.suspense_list"), h = Symbol.for("react.memo"), x = Symbol.for("react.lazy"), p = Symbol.for("react.offscreen"), R = Symbol.iterator, j = "@@iterator";
function J(e) {
if (e === null || typeof e != "object")
return null;
var r = R && e[R] || e[j];
return typeof r == "function" ? r : null;
}
var O = N.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
function g(e) {
{
for (var r = arguments.length, t = new Array(r > 1 ? r - 1 : 0), n = 1; n < r; n++)
t[n - 1] = arguments[n];
B("error", e, t);
}
}
function B(e, r, t) {
{
var n = O.ReactDebugCurrentFrame, o = n.getStackAddendum();
o !== "" && (r += "%s", t = t.concat([o]));
var s = t.map(function(i) {
return String(i);
});
s.unshift("Warning: " + r), Function.prototype.apply.call(console[e], console, s);
}
}
var D = !1, z = !1, De = !1, Ae = !1, Fe = !1, ae;
ae = Symbol.for("react.module.reference");
function Ie(e) {
return !!(typeof e == "string" || typeof e == "function" || e === u || e === S || Fe || e === E || e === v || e === m || Ae || e === p || D || z || De || typeof e == "object" && e !== null && (e.$$typeof === x || e.$$typeof === h || e.$$typeof === T || e.$$typeof === b || e.$$typeof === d || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
// with.
e.$$typeof === ae || e.getModuleId !== void 0));
}
function $e(e, r, t) {
var n = e.displayName;
if (n)
return n;
var o = r.displayName || r.name || "";
return o !== "" ? t + "(" + o + ")" : t;
}
function ie(e) {
return e.displayName || "Context";
}
function k(e) {
if (e == null)
return null;
if (typeof e.tag == "number" && g("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."), typeof e == "function")
return e.displayName || e.name || null;
if (typeof e == "string")
return e;
switch (e) {
case u:
return "Fragment";
case C:
return "Portal";
case S:
return "Profiler";
case E:
return "StrictMode";
case v:
return "Suspense";
case m:
return "SuspenseList";
}
if (typeof e == "object")
switch (e.$$typeof) {
case b:
var r = e;
return ie(r) + ".Consumer";
case T:
var t = e;
return ie(t._context) + ".Provider";
case d:
return $e(e, e.render, "ForwardRef");
case h:
var n = e.displayName || null;
return n !== null ? n : k(e.type) || "Memo";
case x: {
var o = e, s = o._payload, i = o._init;
try {
return k(i(s));
} catch {
return null;
}
}
}
return null;
}
var A = Object.assign, $ = 0, oe, se, le, ue, ce, fe, de;
function ve() {
}
ve.__reactDisabledLog = !0;
function We() {
{
if ($ === 0) {
oe = console.log, se = console.info, le = console.warn, ue = console.error, ce = console.group, fe = console.groupCollapsed, de = console.groupEnd;
var e = {
configurable: !0,
enumerable: !0,
value: ve,
writable: !0
};
Object.defineProperties(console, {
info: e,
log: e,
warn: e,
error: e,
group: e,
groupCollapsed: e,
groupEnd: e
});
}
$++;
}
}
function Ye() {
{
if ($--, $ === 0) {
var e = {
configurable: !0,
enumerable: !0,
writable: !0
};
Object.defineProperties(console, {
log: A({}, e, {
value: oe
}),
info: A({}, e, {
value: se
}),
warn: A({}, e, {
value: le
}),
error: A({}, e, {
value: ue
}),
group: A({}, e, {
value: ce
}),
groupCollapsed: A({}, e, {
value: fe
}),
groupEnd: A({}, e, {
value: de
})
});
}
$ < 0 && g("disabledDepth fell below zero. This is a bug in React. Please file an issue.");
}
}
var H = O.ReactCurrentDispatcher, K;
function V(e, r, t) {
{
if (K === void 0)
try {
throw Error();
} catch (o) {
var n = o.stack.trim().match(/\n( *(at )?)/);
K = n && n[1] || "";
}
return `
` + K + e;
}
}
var X = !1, M;
{
var Le = typeof WeakMap == "function" ? WeakMap : Map;
M = new Le();
}
function pe(e, r) {
if (!e || X)
return "";
{
var t = M.get(e);
if (t !== void 0)
return t;
}
var n;
X = !0;
var o = Error.prepareStackTrace;
Error.prepareStackTrace = void 0;
var s;
s = H.current, H.current = null, We();
try {
if (r) {
var i = function() {
throw Error();
};
if (Object.defineProperty(i.prototype, "props", {
set: function() {
throw Error();
}
}), typeof Reflect == "object" && Reflect.construct) {
try {
Reflect.construct(i, []);
} catch (P) {
n = P;
}
Reflect.construct(e, [], i);
} else {
try {
i.call();
} catch (P) {
n = P;
}
e.call(i.prototype);
}
} else {
try {
throw Error();
} catch (P) {
n = P;
}
e();
}
} catch (P) {
if (P && n && typeof P.stack == "string") {
for (var a = P.stack.split(`
`), y = n.stack.split(`
`), c = a.length - 1, f = y.length - 1; c >= 1 && f >= 0 && a[c] !== y[f]; )
f--;
for (; c >= 1 && f >= 0; c--, f--)
if (a[c] !== y[f]) {
if (c !== 1 || f !== 1)
do
if (c--, f--, f < 0 || a[c] !== y[f]) {
var _ = `
` + a[c].replace(" at new ", " at ");
return e.displayName && _.includes("<anonymous>") && (_ = _.replace("<anonymous>", e.displayName)), typeof e == "function" && M.set(e, _), _;
}
while (c >= 1 && f >= 0);
break;
}
}
} finally {
X = !1, H.current = s, Ye(), Error.prepareStackTrace = o;
}
var I = e ? e.displayName || e.name : "", je = I ? V(I) : "";
return typeof e == "function" && M.set(e, je), je;
}
function Ve(e, r, t) {
return pe(e, !1);
}
function Me(e) {
var r = e.prototype;
return !!(r && r.isReactComponent);
}
function U(e, r, t) {
if (e == null)
return "";
if (typeof e == "function")
return pe(e, Me(e));
if (typeof e == "string")
return V(e);
switch (e) {
case v:
return V("Suspense");
case m:
return V("SuspenseList");
}
if (typeof e == "object")
switch (e.$$typeof) {
case d:
return Ve(e.render);
case h:
return U(e.type, r, t);
case x: {
var n = e, o = n._payload, s = n._init;
try {
return U(s(o), r, t);
} catch {
}
}
}
return "";
}
var G = Object.prototype.hasOwnProperty, he = {}, me = O.ReactDebugCurrentFrame;
function q(e) {
if (e) {
var r = e._owner, t = U(e.type, e._source, r ? r.type : null);
me.setExtraStackFrame(t);
} else
me.setExtraStackFrame(null);
}
function Ue(e, r, t, n, o) {
{
var s = Function.call.bind(G);
for (var i in e)
if (s(e, i)) {
var a = void 0;
try {
if (typeof e[i] != "function") {
var y = Error((n || "React class") + ": " + t + " type `" + i + "` is invalid; it must be a function, usually from the `prop-types` package, but received `" + typeof e[i] + "`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");
throw y.name = "Invariant Violation", y;
}
a = e[i](r, i, n, t, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
} catch (c) {
a = c;
}
a && !(a instanceof Error) && (q(o), g("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).", n || "React class", t, i, typeof a), q(null)), a instanceof Error && !(a.message in he) && (he[a.message] = !0, q(o), g("Failed %s type: %s", t, a.message), q(null));
}
}
}
var Ge = Array.isArray;
function Z(e) {
return Ge(e);
}
function qe(e) {
{
var r = typeof Symbol == "function" && Symbol.toStringTag, t = r && e[Symbol.toStringTag] || e.constructor.name || "Object";
return t;
}
}
function Je(e) {
try {
return ge(e), !1;
} catch {
return !0;
}
}
function ge(e) {
return "" + e;
}
function ye(e) {
if (Je(e))
return g("The provided key is an unsupported type %s. This value must be coerced to a string before using it here.", qe(e)), ge(e);
}
var W = O.ReactCurrentOwner, Be = {
key: !0,
ref: !0,
__self: !0,
__source: !0
}, be, Ee, Q;
Q = {};
function ze(e) {
if (G.call(e, "ref")) {
var r = Object.getOwnPropertyDescriptor(e, "ref").get;
if (r && r.isReactWarning)
return !1;
}
return e.ref !== void 0;
}
function He(e) {
if (G.call(e, "key")) {
var r = Object.getOwnPropertyDescriptor(e, "key").get;
if (r && r.isReactWarning)
return !1;
}
return e.key !== void 0;
}
function Ke(e, r) {
if (typeof e.ref == "string" && W.current && r && W.current.stateNode !== r) {
var t = k(W.current.type);
Q[t] || (g('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', k(W.current.type), e.ref), Q[t] = !0);
}
}
function Xe(e, r) {
{
var t = function() {
be || (be = !0, g("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", r));
};
t.isReactWarning = !0, Object.defineProperty(e, "key", {
get: t,
configurable: !0
});
}
}
function Ze(e, r) {
{
var t = function() {
Ee || (Ee = !0, g("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", r));
};
t.isReactWarning = !0, Object.defineProperty(e, "ref", {
get: t,
configurable: !0
});
}
}
var Qe = function(e, r, t, n, o, s, i) {
var a = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: w,
// Built-in properties that belong on the element
type: e,
key: r,
ref: t,
props: i,
// Record the component responsible for creating this element.
_owner: s
};
return a._store = {}, Object.defineProperty(a._store, "validated", {
configurable: !1,
enumerable: !1,
writable: !0,
value: !1
}), Object.defineProperty(a, "_self", {
configurable: !1,
enumerable: !1,
writable: !1,
value: n
}), Object.defineProperty(a, "_source", {
configurable: !1,
enumerable: !1,
writable: !1,
value: o
}), Object.freeze && (Object.freeze(a.props), Object.freeze(a)), a;
};
function er(e, r, t, n, o) {
{
var s, i = {}, a = null, y = null;
t !== void 0 && (ye(t), a = "" + t), He(r) && (ye(r.key), a = "" + r.key), ze(r) && (y = r.ref, Ke(r, o));
for (s in r)
G.call(r, s) && !Be.hasOwnProperty(s) && (i[s] = r[s]);
if (e && e.defaultProps) {
var c = e.defaultProps;
for (s in c)
i[s] === void 0 && (i[s] = c[s]);
}
if (a || y) {
var f = typeof e == "function" ? e.displayName || e.name || "Unknown" : e;
a && Xe(i, f), y && Ze(i, f);
}
return Qe(e, a, y, o, n, W.current, i);
}
}
var ee = O.ReactCurrentOwner, xe = O.ReactDebugCurrentFrame;
function F(e) {
if (e) {
var r = e._owner, t = U(e.type, e._source, r ? r.type : null);
xe.setExtraStackFrame(t);
} else
xe.setExtraStackFrame(null);
}
var re;
re = !1;
function te(e) {
return typeof e == "object" && e !== null && e.$$typeof === w;
}
function _e() {
{
if (ee.current) {
var e = k(ee.current.type);
if (e)
return `
Check the render method of \`` + e + "`.";
}
return "";
}
}
function rr(e) {
{
if (e !== void 0) {
var r = e.fileName.replace(/^.*[\\\/]/, ""), t = e.lineNumber;
return `
Check your code at ` + r + ":" + t + ".";
}
return "";
}
}
var Re = {};
function tr(e) {
{
var r = _e();
if (!r) {
var t = typeof e == "string" ? e : e.displayName || e.name;
t && (r = `
Check the top-level render call using <` + t + ">.");
}
return r;
}
}
function we(e, r) {
{
if (!e._store || e._store.validated || e.key != null)
return;
e._store.validated = !0;
var t = tr(r);
if (Re[t])
return;
Re[t] = !0;
var n = "";
e && e._owner && e._owner !== ee.current && (n = " It was passed a child from " + k(e._owner.type) + "."), F(e), g('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.', t, n), F(null);
}
}
function Te(e, r) {
{
if (typeof e != "object")
return;
if (Z(e))
for (var t = 0; t < e.length; t++) {
var n = e[t];
te(n) && we(n, r);
}
else if (te(e))
e._store && (e._store.validated = !0);
else if (e) {
var o = J(e);
if (typeof o == "function" && o !== e.entries)
for (var s = o.call(e), i; !(i = s.next()).done; )
te(i.value) && we(i.value, r);
}
}
}
function nr(e) {
{
var r = e.type;
if (r == null || typeof r == "string")
return;
var t;
if (typeof r == "function")
t = r.propTypes;
else if (typeof r == "object" && (r.$$typeof === d || // Note: Memo only checks outer props here.
// Inner props are checked in the reconciler.
r.$$typeof === h))
t = r.propTypes;
else
return;
if (t) {
var n = k(r);
Ue(t, e.props, "prop", n, e);
} else if (r.PropTypes !== void 0 && !re) {
re = !0;
var o = k(r);
g("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?", o || "Unknown");
}
typeof r.getDefaultProps == "function" && !r.getDefaultProps.isReactClassApproved && g("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.");
}
}
function ar(e) {
{
for (var r = Object.keys(e.props), t = 0; t < r.length; t++) {
var n = r[t];
if (n !== "children" && n !== "key") {
F(e), g("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.", n), F(null);
break;
}
}
e.ref !== null && (F(e), g("Invalid attribute `ref` supplied to `React.Fragment`."), F(null));
}
}
function Se(e, r, t, n, o, s) {
{
var i = Ie(e);
if (!i) {
var a = "";
(e === void 0 || typeof e == "object" && e !== null && Object.keys(e).length === 0) && (a += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.");
var y = rr(o);
y ? a += y : a += _e();
var c;
e === null ? c = "null" : Z(e) ? c = "array" : e !== void 0 && e.$$typeof === w ? (c = "<" + (k(e.type) || "Unknown") + " />", a = " Did you accidentally export a JSX literal instead of a component?") : c = typeof e, g("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s", c, a);
}
var f = er(e, r, t, o, s);
if (f == null)
return f;
if (i) {
var _ = r.children;
if (_ !== void 0)
if (n)
if (Z(_)) {
for (var I = 0; I < _.length; I++)
Te(_[I], e);
Object.freeze && Object.freeze(_);
} else
g("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");
else
Te(_, e);
}
return e === u ? ar(f) : nr(f), f;
}
}
function ir(e, r, t) {
return Se(e, r, t, !0);
}
function or(e, r, t) {
return Se(e, r, t, !1);
}
var sr = or, lr = ir;
L.Fragment = u, L.jsx = sr, L.jsxs = lr;
}()), L;
}
process.env.NODE_ENV === "production" ? ne.exports = cr() : ne.exports = fr();
var l = ne.exports;
function dr({
question: N = "",
apiKey: w = "",
selectedDocs: C = "",
history: u = [],
conversationId: E = null,
apiHost: S = "",
onEvent: T = () => {
console.log("Event triggered, but no handler provided.");
}
}) {
let b = "default";
return C && (b = C), new Promise((d, v) => {
const m = {
question: N,
api_key: w,
embeddings_key: w,
active_docs: b,
history: JSON.stringify(u),
conversation_id: E,
model: "default"
};
fetch(S + "/stream", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(m)
}).then((h) => {
if (!h.body)
throw Error("No response body");
const x = h.body.getReader(), p = new TextDecoder("utf-8");
let R = 0;
const j = ({
done: J,
value: O
}) => {
if (J) {
console.log(R), d();
return;
}
R += 1;
const B = p.decode(O).split(`
`);
for (let D of B) {
if (D.trim() == "")
continue;
D.startsWith("data:") && (D = D.substring(5));
const z = new MessageEvent("message", {
data: D
});
T(z);
}
x.read().then(j).catch(v);
};
x.read().then(j).catch(v);
}).catch((h) => {
console.error("Connection failed:", h), v(h);
});
});
}
const pr = ({ apiHost: N = "https://gptcloud.arc53.com", selectDocs: w = "default", apiKey: C = "docsgpt-public" }) => {
const [u, E] = ke(() => typeof window < "u" && localStorage.getItem("docsGPTChatState") || "init"), [S, T] = ke(""), b = ur(null);
Pe(() => {
if (b.current) {
const v = b.current;
v.scrollTop = v.scrollHeight;
}
}, [S]), Pe(() => {
localStorage.setItem("docsGPTChatState", u);
}, [u]);
const d = (v) => {
T(""), v.preventDefault(), E(
"processing"
/* Processing */
), setTimeout(() => {
E(
"answer"
/* Answer */
);
}, 800);
const h = v.currentTarget[0].value;
dr({
question: h,
apiKey: C,
selectedDocs: w,
history: [],
conversationId: null,
apiHost: N,
onEvent: (x) => {
const p = JSON.parse(x.data);
if (p.type === "end")
E(
"answer"
/* Answer */
);
else if (p.type === "source") {
let R;
if (p.metadata && p.metadata.title) {
const j = p.metadata.title.split("/");
R = {
title: j[j.length - 1],
text: p.doc
};
} else
R = { title: p.doc, text: p.doc };
console.log(R);
} else if (p.type === "id")
console.log(p.id);
else {
const R = p.answer;
T((j) => j + R);
}
}
});
};
return /* @__PURE__ */ l.jsx(l.Fragment, { children: /* @__PURE__ */ l.jsxs("div", { className: "dark widget-container", children: [
/* @__PURE__ */ l.jsx(
"div",
{
onClick: () => E(
"init"
/* Init */
),
className: `${u !== "minimized" ? "hidden" : ""} cursor-pointer`,
children: /* @__PURE__ */ l.jsx("div", { className: "mr-2 mb-2 w-20 h-20 rounded-full overflow-hidden dark:divide-gray-700 border dark:border-gray-700 bg-gradient-to-br from-gray-100/80 via-white to-white dark:from-gray-900/80 dark:via-gray-900 dark:to-gray-900 font-sans shadow backdrop-blur-sm flex items-center justify-center", children: /* @__PURE__ */ l.jsx(
"img",
{
src: "https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png",
alt: "DocsGPT",
className: "cursor-pointer hover:opacity-50 h-14"
}
) })
}
),
/* @__PURE__ */ l.jsxs("div", { className: ` ${u !== "minimized" ? "" : "hidden"} divide-y dark:divide-gray-700 rounded-md border dark:border-gray-700 bg-gradient-to-br from-gray-100/80 via-white to-white dark:from-gray-900/80 dark:via-gray-900 dark:to-gray-900 font-sans shadow backdrop-blur-sm`, style: { width: "18rem", transform: "translateY(0%) translateZ(0px)" }, children: [
/* @__PURE__ */ l.jsxs("div", { children: [
/* @__PURE__ */ l.jsx(
"img",
{
src: "https://d3dg1063dc54p9.cloudfront.net/exit.svg",
alt: "Exit",
className: "cursor-pointer hover:opacity-50 h-2 absolute top-0 right-0 m-2 white-filter",
onClick: (v) => {
v.stopPropagation(), E(
"minimized"
/* Minimized */
);
}
}
),
/* @__PURE__ */ l.jsxs("div", { className: "flex items-center gap-2 p-3", children: [
/* @__PURE__ */ l.jsxs("div", { className: `${u === "init" || u === "processing" || u === "typing" ? "" : "hidden"} flex-1`, children: [
/* @__PURE__ */ l.jsx("h3", { className: "text-sm font-bold text-gray-700 dark:text-gray-200", children: "Need help with documentation?" }),
/* @__PURE__ */ l.jsx("p", { className: "mt-1 text-xs text-gray-400 dark:text-gray-500", children: "DocsGPT AI assistant will help you with docs" })
] }),
/* @__PURE__ */ l.jsx("div", { id: "docsgpt-answer", ref: b, className: `${u !== "answer" ? "hidden" : ""}`, children: /* @__PURE__ */ l.jsx("p", { className: "mt-1 text-sm text-gray-600 dark:text-white text-left", children: S }) })
] })
] }),
/* @__PURE__ */ l.jsxs("div", { className: "w-full", children: [
/* @__PURE__ */ l.jsx(
"button",
{
onClick: () => E(
"typing"
/* Typing */
),
className: `flex w-full justify-center px-5 py-3 text-sm text-gray-800 font-bold dark:text-white transition duration-300 hover:bg-gray-100 rounded-b dark:hover:bg-gray-800/70 ${u !== "init" ? "hidden" : ""}`,
children: "Ask DocsGPT"
}
),
(u === "typing" || u === "answer") && /* @__PURE__ */ l.jsxs(
"form",
{
onSubmit: d,
className: "relative w-full m-0",
style: { opacity: 1 },
children: [
/* @__PURE__ */ l.jsx(
"input",
{
type: "text",
className: "w-full bg-transparent px-5 py-3 pr-8 text-sm text-gray-700 dark:text-white focus:outline-none",
placeholder: "What do you want to do?"
}
),
/* @__PURE__ */ l.jsx("button", { className: "absolute text-gray-400 dark:text-gray-500 text-sm inset-y-0 right-2 -mx-2 px-2", type: "submit", children: "Submit" })
]
}
),
/* @__PURE__ */ l.jsxs("p", { className: `${u !== "processing" ? "hidden" : ""} flex w-full justify-center px-5 py-3 text-sm text-gray-800 font-bold dark:text-white transition duration-300 rounded-b`, children: [
"Processing",
/* @__PURE__ */ l.jsx("span", { className: "dot-animation", children: "." }),
/* @__PURE__ */ l.jsx("span", { className: "dot-animation delay-200", children: "." }),
/* @__PURE__ */ l.jsx("span", { className: "dot-animation delay-400", children: "." })
] })
] })
] })
] }) });
};
export {
pr as DocsGPTWidget
};
//# sourceMappingURL=index.es.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
export declare const DocsGPTWidget: ({ apiHost, selectDocs, apiKey }: {
apiHost?: string | undefined;
selectDocs?: string | undefined;
apiKey?: string | undefined;
}) => JSX.Element;

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,762 +0,0 @@
/*
! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com
*//*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: #e5e7eb; /* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
*/
html {
line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
-o-tab-size: 4;
tab-size: 4; /* 3 */
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */
font-feature-settings: normal; /* 5 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0; /* 1 */
line-height: inherit; /* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0; /* 1 */
color: inherit; /* 2 */
border-top-width: 1px; /* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* 1 */
font-size: 1em; /* 2 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0; /* 1 */
border-color: inherit; /* 2 */
border-collapse: collapse; /* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
font-weight: inherit; /* 1 */
line-height: inherit; /* 1 */
color: inherit; /* 1 */
margin: 0; /* 2 */
padding: 0; /* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button; /* 1 */
background-color: transparent; /* 2 */
background-image: none; /* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1; /* 1 */
color: #9ca3af; /* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1; /* 1 */
color: #9ca3af; /* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden] {
display: none;
}
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.inset-y-0 {
top: 0px;
bottom: 0px;
}
.top-0 {
top: 0px;
}
.right-0 {
right: 0px;
}
.right-2 {
right: 0.5rem;
}
.m-2 {
margin: 0.5rem;
}
.m-0 {
margin: 0px;
}
.-mx-2 {
margin-left: -0.5rem;
margin-right: -0.5rem;
}
.mr-2 {
margin-right: 0.5rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
.mt-1 {
margin-top: 0.25rem;
}
.flex {
display: flex;
}
.hidden {
display: none;
}
.h-20 {
height: 5rem;
}
.h-14 {
height: 3.5rem;
}
.h-2 {
height: 0.5rem;
}
.w-20 {
width: 5rem;
}
.w-full {
width: 100%;
}
.flex-1 {
flex: 1 1 0%;
}
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.cursor-pointer {
cursor: pointer;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.gap-2 {
gap: 0.5rem;
}
.divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
.overflow-hidden {
overflow: hidden;
}
.rounded-full {
border-radius: 9999px;
}
.rounded-md {
border-radius: 0.375rem;
}
.rounded-b {
border-bottom-right-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
}
.border {
border-width: 1px;
}
.bg-transparent {
background-color: transparent;
}
.bg-gradient-to-br {
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
}
.from-gray-100\/80 {
--tw-gradient-from: rgb(243 244 246 / 0.8);
--tw-gradient-to: rgb(243 244 246 / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.via-white {
--tw-gradient-to: rgb(255 255 255 / 0);
--tw-gradient-stops: var(--tw-gradient-from), #fff, var(--tw-gradient-to);
}
.to-white {
--tw-gradient-to: #fff;
}
.p-3 {
padding: 0.75rem;
}
.px-5 {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.py-3 {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.pr-8 {
padding-right: 2rem;
}
.text-left {
text-align: left;
}
.font-sans {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-xs {
font-size: 0.75rem;
line-height: 1rem;
}
.font-bold {
font-weight: 700;
}
.text-gray-700 {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
}
.text-gray-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
}
.text-gray-600 {
--tw-text-opacity: 1;
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.text-gray-800 {
--tw-text-opacity: 1;
color: rgb(31 41 55 / var(--tw-text-opacity));
}
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.backdrop-blur-sm {
--tw-backdrop-blur: blur(4px);
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.delay-200 {
transition-delay: 200ms;
}
.duration-300 {
transition-duration: 300ms;
}
#docsgpt-answer {
max-height: 50vh; /* 50% of the viewport height */
overflow-y: auto; /* Adds a vertical scrollbar if the content exceeds the container height */
}
.widget-container {
position: fixed; /* fixed positioning */
right: 10px; /* from the right edge */
bottom: 10px; /* from the bottom edge */
z-index: 1000; /* to ensure it appears on top of other content, if any */
display: flex;
flex-direction: column;
align-items: center;
}
@keyframes dotBounce {
0%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-5px);
}
}
.dot-animation {
display: inline-block;
animation: dotBounce 1s infinite ease-in-out;
}
.delay-200 {
animation-delay: 200ms;
}
.delay-400 {
animation-delay: 400ms;
}
.white-filter {
filter: invert(1) brightness(2);
}
.hover\:bg-gray-100:hover {
--tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
}
.hover\:opacity-50:hover {
opacity: 0.5;
}
.focus\:outline-none:focus {
outline: 2px solid transparent;
outline-offset: 2px;
}
@media (prefers-color-scheme: dark) {
.dark\:divide-gray-700 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-divide-opacity));
}
.dark\:border-gray-700 {
--tw-border-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-border-opacity));
}
.dark\:from-gray-900\/80 {
--tw-gradient-from: rgb(17 24 39 / 0.8);
--tw-gradient-to: rgb(17 24 39 / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.dark\:via-gray-900 {
--tw-gradient-to: rgb(17 24 39 / 0);
--tw-gradient-stops: var(--tw-gradient-from), #111827, var(--tw-gradient-to);
}
.dark\:to-gray-900 {
--tw-gradient-to: #111827;
}
.dark\:text-gray-200 {
--tw-text-opacity: 1;
color: rgb(229 231 235 / var(--tw-text-opacity));
}
.dark\:text-gray-500 {
--tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.dark\:text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.dark\:hover\:bg-gray-800\/70:hover {
background-color: rgb(31 41 55 / 0.7);
}
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1 +0,0 @@
export { DocsGPTWidget } from "./src/components/DocsGPTWidget";

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +1,65 @@
{
"name": "docsgpt",
"version": "0.3.7",
"private": false,
"version": "0.2.4",
"type": "module",
"main": "dist/index.umd.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.umd.js",
"types": "./dist/index.d.ts"
},
"./dist/style.css": "./dist/style.css"
},
"description": "DocsGPT 🦖 is an innovative open-source tool designed to simplify the retrieval of information from project documentation using advanced GPT models 🤖.",
"source": "./src/index.html",
"main": "dist/main.js",
"module": "dist/module.js",
"types": "dist/types.d.ts",
"files": [
"/dist"
"dist",
"package.json"
],
"publishConfig": {
"access": "public"
"@parcel/resolver-default": {
"packageExports": true
},
"resolution": {
"styled-components": "^5"
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"prepare": "npm run build && npm run build-css",
"build-css": "postcss src/index.css -o dist/style.css",
"preview": "vite preview"
"build": "parcel build src/index.ts",
"dev": "parcel",
"test": "jest",
"lint": "eslint",
"check": "tsc --noEmit",
"ci": "yarn build && yarn test && yarn lint && yarn check"
},
"dependencies": {
"postcss-cli": "^10.1.0",
"@babel/plugin-transform-flow-strip-types": "^7.23.3",
"@bpmn-io/snarkdown": "^2.2.0",
"@parcel/resolver-glob": "^2.12.0",
"@parcel/transformer-svg-react": "^2.12.0",
"@parcel/transformer-typescript-tsc": "^2.12.0",
"@parcel/validator-typescript": "^2.12.0",
"@radix-ui/react-icons": "^1.3.0",
"@types/react": "^18.2.61",
"@types/react-dom": "^18.2.19",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"dompurify": "^3.0.9",
"flow-bin": "^0.229.2",
"i": "^0.3.7",
"install": "^0.13.0",
"npm": "^10.5.0",
"parcel": "^2.12.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.2.4"
"styled-components": "^6.1.8"
},
"devDependencies": {
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react-swc": "^3.0.0",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.31",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vite-plugin-dts": "^1.7.1"
"@babel/core": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"@babel/preset-react": "^7.23.3",
"@parcel/packager-ts": "^2.12.0",
"@parcel/transformer-typescript-types": "^2.12.0",
"@types/dompurify": "^3.0.5",
"babel-loader": "^8.0.4",
"process": "^0.11.10",
"typescript": "^5.3.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,5 +0,0 @@

View File

@@ -1,15 +1,11 @@
import { useState } from "react";
//import "./App.css";
import {DocsGPTWidget} from "./components/DocsGPTWidget";
function App() {
const [count, setCount] = useState(0);
import React from "react"
import {DocsGPTWidget} from "./components/DocsGPTWidget"
const App = () => {
return (
<div className="App">
<DocsGPTWidget />
<div>
<DocsGPTWidget/>
</div>
);
)
}
export default App;
export default App

View File

@@ -0,0 +1,7 @@
<svg width="36" height="36" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.37891 9.44824H7.75821" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.1377 9.44824H12.8273" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.37891 6.06934H6.06856" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.44824 6.06934H12.8276" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.2069 11.1379C16.2069 11.5861 16.0289 12.0158 15.712 12.3327C15.3951 12.6496 14.9654 12.8276 14.5172 12.8276H4.37931L1 16.2069V2.68965C1 2.24153 1.17802 1.81176 1.49489 1.49489C1.81176 1.17802 2.24153 1 2.68965 1H14.5172C14.9654 1 15.3951 1.17802 15.712 1.49489C16.0289 1.81176 16.2069 2.24153 16.2069 2.68965V11.1379Z" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1009 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,247 +1,460 @@
"use client";
import {useEffect, useRef, useState} from 'react'
//import './style.css'
interface HistoryItem {
prompt: string;
response: string;
import { Fragment, useEffect, useRef, useState } from 'react'
import { PaperPlaneIcon, RocketIcon, ExclamationTriangleIcon, Cross2Icon } from '@radix-ui/react-icons';
import { MESSAGE_TYPE } from '../models/types';
import { Query, Status } from '../models/types';
import MessageIcon from '../assets/message.svg'
import { fetchAnswerStreaming } from '../requests/streamingApi';
import styled, { keyframes, createGlobalStyle } from 'styled-components';
import snarkdown from '@bpmn-io/snarkdown';
import { sanitize } from 'dompurify';
const GlobalStyles = createGlobalStyle`
.response pre {
padding: 8px;
width: 90%;
font-size: 12px;
border-radius: 6px;
overflow-x: auto;
background-color: #1B1C1F;
}
interface FetchAnswerStreamingProps {
question?: string;
apiKey?: string;
selectedDocs?: string;
history?: HistoryItem[];
conversationId?: string | null;
apiHost?: string;
onEvent?: (event: MessageEvent) => void;
.response h1{
font-size: 20px;
}
enum ChatStates {
Init = 'init',
Processing = 'processing',
Typing = 'typing',
Answer = 'answer',
Minimized = 'minimized',
.response h2{
font-size: 18px;
}
function fetchAnswerStreaming({
question = '',
apiKey = '',
selectedDocs = '',
history = [],
conversationId = null,
apiHost = '',
onEvent = () => {console.log("Event triggered, but no handler provided.");}
}: FetchAnswerStreamingProps): Promise<void> {
let docPath = 'default';
if (selectedDocs) {
docPath = selectedDocs;
}
return new Promise<void>((resolve, reject) => {
const body = {
question: question,
api_key: apiKey,
embeddings_key: apiKey,
active_docs: docPath,
history: JSON.stringify(history),
conversation_id: conversationId,
model: 'default'
};
fetch(apiHost + '/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
.then((response) => {
if (!response.body) throw Error('No response body');
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let counterrr = 0;
const processStream = ({
done,
value,
}: ReadableStreamReadResult<Uint8Array>) => {
if (done) {
console.log(counterrr);
resolve();
return;
}
counterrr += 1;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (let line of lines) {
if (line.trim() == '') {
continue;
}
if (line.startsWith('data:')) {
line = line.substring(5);
}
const messageEvent = new MessageEvent('message', {
data: line,
});
onEvent(messageEvent); // handle each message
}
reader.read().then(processStream).catch(reject);
};
reader.read().then(processStream).catch(reject);
})
.catch((error) => {
console.error('Connection failed:', error);
reject(error);
});
});
.response h3{
font-size: 16px;
}
export const DocsGPTWidget = ({ apiHost = 'https://gptcloud.arc53.com', selectDocs = 'default', apiKey = 'docsgpt-public'}) => {
// processing states
const [chatState, setChatState] = useState<ChatStates>(() => {
if (typeof window !== 'undefined') {
return localStorage.getItem('docsGPTChatState') as ChatStates || ChatStates.Init;
}
return ChatStates.Init;
});
const [answer, setAnswer] = useState<string>('');
//const selectDocs = 'local/1706.03762.pdf/'
const answerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (answerRef.current) {
const element = answerRef.current;
element.scrollTop = element.scrollHeight;
}
}, [answer]);
useEffect(() => {
localStorage.setItem('docsGPTChatState', chatState);
}, [chatState]);
// submit handler
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
setAnswer('')
e.preventDefault()
// get question
setChatState(ChatStates.Processing)
setTimeout(() => {
setChatState(ChatStates.Answer)
}, 800)
const inputElement = e.currentTarget[0] as HTMLInputElement;
const questionValue = inputElement.value;
fetchAnswerStreaming({
question: questionValue,
apiKey: apiKey,
selectedDocs: selectDocs,
history: [],
conversationId: null,
apiHost: apiHost,
onEvent: (event) => {
const data = JSON.parse(event.data);
// check if the 'end' event has been received
if (data.type === 'end') {
setChatState(ChatStates.Answer)
} else if (data.type === 'source') {
// check if data.metadata exists
let result;
if (data.metadata && data.metadata.title) {
const titleParts = data.metadata.title.split('/');
result = {
title: titleParts[titleParts.length - 1],
text: data.doc,
};
} else {
result = { title: data.doc, text: data.doc };
}
console.log(result)
} else if (data.type === 'id') {
console.log(data.id);
} else {
const result = data.answer;
// set answer by appending answer
setAnswer(prevAnswer => prevAnswer + result);
}
},
});
.response code:not(pre code){
border-radius: 6px;
padding: 1px 3px 1px 3px;
font-size: 12px;
display: inline-block;
background-color: #646464;
}
`;
const WidgetContainer = styled.div`
display: block;
position: fixed;
right: 10px;
bottom: 10px;
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center;
text-align: left;
`;
const StyledContainer = styled.div`
display: block;
position: relative;
bottom: 0;
left: 0;
width: 352px;
height: 407px;
max-height: 407px;
border-radius: 0.75rem;
background-color: #222327;
font-family: sans-serif;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.1);
transition: visibility 0.3s, opacity 0.3s;
`;
const FloatingButton = styled.div`
position: fixed;
display: flex;
z-index: 500;
justify-content: center;
align-items: center;
bottom: 1rem;
right: 1rem;
width: 5rem;
height: 5rem;
border-radius: 9999px;
background-image: linear-gradient(to bottom right, #5AF0EC, #E80D9D);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
cursor: pointer;
&:hover {
transform: scale(1.1);
transition: transform 0.2s ease-in-out;
}
`;
const CancelButton = styled.button`
cursor: pointer;
position: absolute;
top: 0;
right: 0;
margin: 0.5rem;
width: 30px;
padding: 0;
background-color: transparent;
border: none;
outline: none;
color: inherit;
transition: opacity 0.3s ease;
opacity: 0.6;
&:hover {
opacity: 1;
}
.white-filter {
filter: invert(100%);
}
`;
const Header = styled.div`
display: flex;
align-items: center;
padding-inline: 0.75rem;
padding-top: 1rem;
padding-bottom: 0.5rem;
`;
const IconWrapper = styled.div`
padding: 0.5rem;
`;
const ContentWrapper = styled.div`
flex: 1;
margin-left: 0.5rem;
`;
const Title = styled.h3`
font-size: 1rem;
font-weight: normal;
color: #FAFAFA;
margin-top: 0;
margin-bottom: 0.25rem;
`;
const Description = styled.p`
font-size: 0.85rem;
color: #A1A1AA;
margin-top: 0;
`;
const Conversation = styled.div`
height: 16rem;
padding-inline: 0.5rem;
border-radius: 0.375rem;
text-align: left;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #4a4a4a transparent; /* thumb color track color */
`;
const MessageBubble = styled.div<{ type: MESSAGE_TYPE }>`
display: flex;
font-size: 16px;
justify-content: ${props => props.type === 'QUESTION' ? 'flex-end' : 'flex-start'};
margin: 0.5rem;
`;
const Message = styled.p<{ type: MESSAGE_TYPE }>`
background: ${props => props.type === 'QUESTION' ?
'linear-gradient(to bottom right, #8860DB, #6D42C5)' :
'#38383b'};
color: #ffff;
border: none;
max-width: 80%;
overflow: auto;
margin: 4px;
display: block;
line-height: 1.5;
padding: 0.75rem;
border-radius: 0.375rem;
`;
const ErrorAlert = styled.div`
color: #b91c1c;
border:0.1px solid #b91c1c;
display: flex;
padding:4px;
margin:0.7rem;
opacity: 90%;
max-width: 70%;
font-weight: 400;
border-radius: 0.375rem;
justify-content: space-evenly;
`
//dot loading animation
const dotBounce = keyframes`
0%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-5px);
}
`;
const DotAnimation = styled.div`
display: inline-block;
animation: ${dotBounce} 1s infinite ease-in-out;
`;
// delay classes as styled components
const Delay = styled(DotAnimation) <{ delay: number }>`
animation-delay: ${props => props.delay + 'ms'};
`;
const PromptContainer = styled.form`
background-color: transparent;
height: 36px;
position: absolute;
bottom: 25px;
left: 24px;
right: 24px;
display: flex;
justify-content: space-evenly;
`;
const StyledInput = styled.input`
width: 260px;
height: 36px;
border: 1px solid #686877;
padding-left: 12px;
background-color: transparent;
font-size: 16px;
border-radius: 6px;
color: #ffff;
outline: none;
`;
const StyledButton = styled.button`
display: flex;
justify-content: center;
align-items: center;
background-image: linear-gradient(to bottom right, #5AF0EC, #E80D9D);
border-radius: 6px;
width: 36px;
height: 36px;
margin-left:8px;
padding: 0px;
border: none;
cursor: pointer;
outline: none;
&:hover{
opacity: 90%;
}
&:disabled {
opacity: 60%;
}`;
const HeroContainer = styled.div`
position: absolute;
top: 50%;
left: 50%;
display: flex;
justify-content: center;
align-items: middle;
transform: translate(-50%, -50%);
width: 80%;
background-image: linear-gradient(to bottom right, #5AF0EC, #ff1bf4);
border-radius: 10px;
margin: 0 auto;
padding: 2px;
`;
const HeroWrapper = styled.div`
background-color: #222327;
border-radius: 10px;
font-weight: normal;
padding: 6px;
display: flex;
justify-content: space-between;
`
const HeroTitle = styled.h3`
color: #fff;
font-size: 17px;
margin-bottom: 5px;
padding: 2px;
`;
const HeroDescription = styled.p`
color: #fff;
font-size: 14px;
line-height: 1.5;
`;
const Hero = ({ title, description }: { title: string, description: string }) => {
return (
<>
<div className="dark widget-container">
<div onClick={() => setChatState(ChatStates.Init)}
className={`${chatState !== 'minimized' ? 'hidden' : ''} cursor-pointer`}>
<div className="mr-2 mb-2 w-20 h-20 rounded-full overflow-hidden dark:divide-gray-700 border dark:border-gray-700 bg-gradient-to-br from-gray-100/80 via-white to-white dark:from-gray-900/80 dark:via-gray-900 dark:to-gray-900 font-sans shadow backdrop-blur-sm flex items-center justify-center">
<img
src="https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"
alt="DocsGPT"
className="cursor-pointer hover:opacity-50 h-14"
/>
</div>
</div>
<div className={` ${chatState !== 'minimized' ? '' : 'hidden'} divide-y dark:divide-gray-700 rounded-md border dark:border-gray-700 bg-gradient-to-br from-gray-100/80 via-white to-white dark:from-gray-900/80 dark:via-gray-900 dark:to-gray-900 font-sans shadow backdrop-blur-sm`} style={{ width: '18rem', transform: 'translateY(0%) translateZ(0px)' }}>
<div>
<img
src="https://d3dg1063dc54p9.cloudfront.net/exit.svg"
alt="Exit"
className="cursor-pointer hover:opacity-50 h-2 absolute top-0 right-0 m-2 white-filter"
onClick={(event) => {
event.stopPropagation();
setChatState(ChatStates.Minimized);
}}
/>
<div className="flex items-center gap-2 p-3">
<div className={`${chatState === 'init' ? '' :
chatState === 'processing' ? '' :
chatState === 'typing' ? '' :
'hidden'} flex-1`}>
<h3 className="text-sm font-bold text-gray-700 dark:text-gray-200">Need help with documentation?</h3>
<p className="mt-1 text-xs text-gray-400 dark:text-gray-500">DocsGPT AI assistant will help you with docs</p>
</div>
<div id="docsgpt-answer" ref={answerRef} className={`${chatState !== 'answer' ? 'hidden' : ''}`}>
<p className="mt-1 text-sm text-gray-600 dark:text-white text-left">{answer}</p>
</div>
<HeroContainer>
<HeroWrapper>
<IconWrapper style={{ marginTop: '8px' }}>
<RocketIcon color='white' width={20} height={20} />
</IconWrapper>
<div>
<HeroTitle>{title}</HeroTitle>
<HeroDescription>
{description}
</HeroDescription>
</div>
</div>
<div className="w-full">
<button onClick={() => setChatState(ChatStates.Typing)}
className={`flex w-full justify-center px-5 py-3 text-sm text-gray-800 font-bold dark:text-white transition duration-300 hover:bg-gray-100 rounded-b dark:hover:bg-gray-800/70 ${chatState !== 'init' ? 'hidden' : ''}`}>
Ask DocsGPT
</button>
{ (chatState === 'typing' || chatState === 'answer') && (
<form
onSubmit={handleSubmit}
className="relative w-full m-0" style={{ opacity: 1 }}>
<input type="text"
className="w-full bg-transparent px-5 py-3 pr-8 text-sm text-gray-700 dark:text-white focus:outline-none" placeholder="What do you want to do?" />
<button className="absolute text-gray-400 dark:text-gray-500 text-sm inset-y-0 right-2 -mx-2 px-2" type="submit" >Submit</button>
</form>
)}
<p className={`${chatState !== 'processing' ? 'hidden' : ''} flex w-full justify-center px-5 py-3 text-sm text-gray-800 font-bold dark:text-white transition duration-300 rounded-b`}>
Processing<span className="dot-animation">.</span><span className="dot-animation delay-200">.</span><span className="dot-animation delay-400">.</span>
</p>
</div>
</div>
</div>
</HeroWrapper>
</HeroContainer>
</>
);
};
export const DocsGPTWidget = ({
apiHost = 'https://gptcloud.arc53.com',
selectDocs = 'default',
apiKey = '82962c9a-aa77-4152-94e5-a4f84fd44c6a',
avatar = 'https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png',
title = 'Get AI assistance',
description = 'DocsGPT\'s AI Chatbot is here to help',
heroTitle = 'Welcome to DocsGPT !',
heroDescription = 'This chatbot is built with DocsGPT and utilises GenAI, please review important information using sources.'
}) => {
const [prompt, setPrompt] = useState('');
const [status, setStatus] = useState<Status>('idle');
const [queries, setQueries] = useState<Query[]>([])
const [conversationId, setConversationId] = useState<string | null>(null)
const [open, setOpen] = useState<boolean>(false)
const [eventInterrupt, setEventInterrupt] = useState<boolean>(false); //click or scroll by user while autoScrolling
const endMessageRef = useRef<HTMLDivElement | null>(null);
const handleUserInterrupt = () => {
(status === 'loading') && setEventInterrupt(true);
}
const scrollToBottom = (element: Element | null) => {
//recursive function to scroll to the last child of the last child ...
// to get to the bottom most element
if (!element) return;
if (element?.children.length === 0) {
element?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
const lastChild = element?.children?.[element.children.length - 1]
lastChild && scrollToBottom(lastChild)
};
useEffect(() => {
!eventInterrupt && scrollToBottom(endMessageRef.current);
}, [queries.length, queries[queries.length - 1]?.response]);
async function stream(question: string) {
setStatus('loading')
try {
await fetchAnswerStreaming(
{
question: question,
apiKey: apiKey,
apiHost: apiHost,
selectedDocs: selectDocs,
history: queries,
conversationId: conversationId,
onEvent: (event: MessageEvent) => {
const data = JSON.parse(event.data);
// check if the 'end' event has been received
if (data.type === 'end') {
// set status to 'idle'
setStatus('idle');
} else if (data.type === 'id') {
setConversationId(data.id)
} else {
const result = data.answer;
const streamingResponse = queries[queries.length - 1].response ? queries[queries.length - 1].response : '';
const updatedQueries = [...queries];
updatedQueries[updatedQueries.length - 1].response = streamingResponse + result;
setQueries(updatedQueries);
}
}
}
);
} catch (error) {
const updatedQueries = [...queries];
updatedQueries[updatedQueries.length - 1].error = 'error'
setQueries(updatedQueries);
setStatus('idle')
//setEventInterrupt(false)
}
}
// submit handler
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setEventInterrupt(false);
queries.push({ prompt })
setPrompt('')
await stream(prompt)
}
const handleImageError = (event: React.SyntheticEvent<HTMLImageElement, Event>) => {
event.currentTarget.src = "https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png";
};
return (
<>
<WidgetContainer>
<GlobalStyles />
{!open && <FloatingButton onClick={() => setOpen(true)} hidden={open}>
<MessageIcon style={{ marginTop: '8px' }} />
</FloatingButton>}
{open && <StyledContainer>
<div>
<CancelButton onClick={() => setOpen(false)}>
<Cross2Icon width={24} height={24} color='white' />
</CancelButton>
<Header>
<IconWrapper>
<img style={{ maxWidth: "42px", maxHeight: "42px" }} onError={handleImageError} src={avatar} alt='docs-gpt' />
</IconWrapper>
<ContentWrapper>
<Title>{title}</Title>
<Description>{description}</Description>
</ContentWrapper>
</Header>
</div>
<Conversation onWheel={handleUserInterrupt} onTouchMove={handleUserInterrupt}>
{
queries.length > 0 ? queries?.map((query, index) => {
return (
<Fragment key={index}>
{
query.prompt && <MessageBubble type='QUESTION'>
<Message
type='QUESTION'
ref={(!(query.response || query.error) && index === queries.length - 1) ? endMessageRef : null}>
{query.prompt}
</Message>
</MessageBubble>
}
{
query.response ? <MessageBubble type='ANSWER'>
<Message
type='ANSWER'
ref={(index === queries.length - 1) ? endMessageRef : null}
>
<div className="response" dangerouslySetInnerHTML={{ __html: sanitize(snarkdown(query.response)) }} />
</Message>
</MessageBubble>
: <div>
{
query.error ? <ErrorAlert>
<IconWrapper>
<ExclamationTriangleIcon style={{ marginTop: '4px' }} width={22} height={22} color='#b91c1c' />
</IconWrapper>
<div>
<h5 style={{ margin: 2 }}>Network Error</h5>
<span style={{ margin: 2, fontSize: '13px' }}>Something went wrong !</span>
</div>
</ErrorAlert>
: <MessageBubble type='ANSWER'>
<Message type='ANSWER' style={{ fontWeight: 600 }}>
<DotAnimation>.</DotAnimation>
<Delay delay={200}>.</Delay>
<Delay delay={400}>.</Delay>
</Message>
</MessageBubble>
}
</div>
}
</Fragment>)
})
: <Hero title={heroTitle} description={heroDescription} />
}
</Conversation>
<PromptContainer
onSubmit={handleSubmit}>
<StyledInput
value={prompt} onChange={(event) => setPrompt(event.target.value)}
type='text' placeholder="What do you want to do?" />
<StyledButton
disabled={prompt.length == 0 || status !== 'idle'}>
<PaperPlaneIcon width={15} height={15} color='white' />
</StyledButton>
</PromptContainer>
</StyledContainer>}
</WidgetContainer>
</>
)
}
}

View File

@@ -1 +0,0 @@
export { DocsGPTWidget } from "./DocsGPTWidget";

View File

@@ -1,44 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
#docsgpt-answer {
max-height: 50vh; /* 50% of the viewport height */
overflow-y: auto; /* Adds a vertical scrollbar if the content exceeds the container height */
}
.widget-container {
position: fixed; /* fixed positioning */
right: 10px; /* from the right edge */
bottom: 10px; /* from the bottom edge */
z-index: 1000; /* to ensure it appears on top of other content, if any */
display: flex;
flex-direction: column;
align-items: center;
}
@keyframes dotBounce {
0%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-5px);
}
}
.dot-animation {
display: inline-block;
animation: dotBounce 1s infinite ease-in-out;
}
.delay-200 {
animation-delay: 200ms;
}
.delay-400 {
animation-delay: 400ms;
}
.white-filter {
filter: invert(1) brightness(2);
}

View File

@@ -2,12 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>DocsGPT Widget</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<div id="app"></div>
<script type="module" src="main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1 @@
export { DocsGPTWidget } from "./components/DocsGPTWidget";

View File

@@ -1,10 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import React from 'react';
const root = createRoot(document.getElementById('app') as HTMLElement);
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
root.render(<App />);

View File

@@ -0,0 +1,92 @@
interface HistoryItem {
prompt: string;
response?: string;
}
interface FetchAnswerStreamingProps {
question?: string;
apiKey?: string;
selectedDocs?: string;
history?: HistoryItem[];
conversationId?: string | null;
apiHost?: string;
onEvent?: (event: MessageEvent) => void;
}
export function fetchAnswerStreaming({
question = '',
apiKey = '',
selectedDocs = '',
history = [],
conversationId = null,
apiHost = '',
onEvent = () => {console.log("Event triggered, but no handler provided.");}
}: FetchAnswerStreamingProps): Promise<void> {
let docPath = 'default';
if (selectedDocs) {
docPath = selectedDocs;
}
return new Promise<void>((resolve, reject) => {
const body = {
question: question,
api_key: apiKey,
embeddings_key: apiKey,
active_docs: docPath,
history: JSON.stringify(history),
conversation_id: conversationId,
model: 'default'
};
fetch(apiHost + '/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
.then((response) => {
if (!response.body) throw Error('No response body');
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let counterrr = 0;
const processStream = ({
done,
value,
}: ReadableStreamReadResult<Uint8Array>) => {
if (done) {
resolve();
return;
}
counterrr += 1;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (let line of lines) {
if (line.trim() == '') {
continue;
}
if (line.startsWith('data:')) {
line = line.substring(5);
}
const messageEvent = new MessageEvent('message', {
data: line,
});
onEvent(messageEvent); // handle each message
}
reader.read().then(processStream).catch(reject);
};
reader.read().then(processStream).catch(reject);
})
.catch((error) => {
console.error('Connection failed:', error);
reject(error);
});
});
}

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,8 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -1,23 +1,34 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"typeRoots": ["./dist/index.d.ts", "node_modules/@types"],
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src", "./index.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*", "@/*"]
},
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* The "typeRoots" configuration specifies the locations where
TypeScript looks for type definitions (.d.ts files) to
include in the compilation process.*/
"typeRoots": ["./dist/index.d.ts", "node_modules/@types"]
},
/* include /index.ts*/
"include": ["src/index.ts","custom.d.ts"],
"exclude": ["node_modules"],
}

View File

@@ -1,9 +0,0 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,26 +0,0 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import dts from "vite-plugin-dts";
import path from "path";
export default defineConfig({
build: {
lib: {
entry: path.resolve(__dirname, "index.ts"),
name: "ViteButton",
fileName: (format) => `index.${format}.js`,
},
rollupOptions: {
external: ["react", "react-dom"],
output: {
globals: {
react: "React",
"react-dom": "ReactDOM",
},
},
},
sourcemap: true,
emptyOutDir: true,
},
plugins: [react(), dts()],
});

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@
"@types/react-syntax-highlighter": "^15.5.6",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"@vitejs/plugin-react": "^3.1.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.13",
"eslint": "^8.33.0",
"eslint-config-prettier": "^8.6.0",
@@ -55,7 +55,7 @@
"prettier-plugin-tailwindcss": "^0.2.2",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.5",
"vite": "^4.5.0",
"vite-plugin-svgr": "^2.4.0"
"vite": "^5.0.13",
"vite-plugin-svgr": "^4.2.0"
}
}

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="33" height="33" viewBox="0 0 33 33" fill="none">
<path d="M8.25 13.75V11C8.25 6.44875 9.625 2.75 16.5 2.75C23.375 2.75 24.75 6.44875 24.75 11V13.75" stroke="#ECECF1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M23.375 30.25H9.625C4.125 30.25 2.75 28.875 2.75 23.375V20.625C2.75 15.125 4.125 13.75 9.625 13.75H23.375C28.875 13.75 30.25 15.125 30.25 20.625V23.375C30.25 28.875 28.875 30.25 23.375 30.25Z" stroke="#ECECF1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.9951 22H22.0075" stroke="#ECECF1" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.4938 22H16.5061" stroke="#ECECF1" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.9924 22H11.0048" stroke="#ECECF1" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 919 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none">
<path d="M12.75 28.4551H12C6 28.4551 3 26.9551 3 19.4551V11.9551C3 5.95508 6 2.95508 12 2.95508H24C30 2.95508 33 5.95508 33 11.9551V19.4551C33 25.4551 30 28.4551 24 28.4551H23.25C22.785 28.4551 22.335 28.6801 22.05 29.0551L19.8 32.0551C18.81 33.3751 17.19 33.3751 16.2 32.0551L13.95 29.0551C13.71 28.7251 13.17 28.4551 12.75 28.4551Z" stroke="#ECECF1" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 13.05L9 16.05L12 19.05" stroke="#ECECF1" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 13.05L27 16.05L24 19.05" stroke="#ECECF1" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 12.5549L16.5 19.545" stroke="#ECECF1" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 980 B

View File

@@ -1,6 +1,10 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.75 28.455H12C6 28.455 3 26.955 3 19.455V11.955C3 5.95496 6 2.95496 12 2.95496H24C30 2.95496 33 5.95496 33 11.955V19.455C33 25.455 30 28.455 24 28.455H23.25C22.785 28.455 22.335 28.68 22.05 29.055L19.8 32.055C18.81 33.375 17.19 33.375 16.2 32.055L13.95 29.055C13.71 28.725 13.17 28.455 12.75 28.455Z" stroke="#3E3434" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 13.05L9 16.05L12 19.05" stroke="#3E3434" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 13.05L27 16.05L24 19.05" stroke="#3E3434" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 12.5549L16.5 19.545" stroke="#3E3434" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<g id="vuesax/linear/message-programming">
<g id="message-programming">
<path id="Vector" d="M12.75 28.455H12C6 28.455 3 26.955 3 19.455V11.955C3 5.95496 6 2.95496 12 2.95496H24C30 2.95496 33 5.95496 33 11.955V19.455C33 25.455 30 28.455 24 28.455H23.25C22.785 28.455 22.335 28.68 22.05 29.055L19.8 32.055C18.81 33.375 17.19 33.375 16.2 32.055L13.95 29.055C13.71 28.725 13.17 28.455 12.75 28.455Z" stroke="#3E3434" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_2" d="M12 13.05L9 16.05L12 19.05" stroke="#3E3434" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_3" d="M24 13.05L27 16.05L24 19.05" stroke="#3E3434" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_4" d="M19.5 12.555L16.5 19.545" stroke="#3E3434" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 951 B

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="29" height="29" viewBox="0 0 29 29" fill="none">
<path d="M10.2708 22.9584H9.66663C4.83329 22.9584 2.41663 21.7501 2.41663 15.7084V9.66675C2.41663 4.83341 4.83329 2.41675 9.66663 2.41675H19.3333C24.1666 2.41675 26.5833 4.83341 26.5833 9.66675V15.7084C26.5833 20.5417 24.1666 22.9584 19.3333 22.9584H18.7291C18.3545 22.9584 17.992 23.1397 17.7625 23.4417L15.95 25.8584C15.1525 26.9217 13.8475 26.9217 13.05 25.8584L11.2375 23.4417C11.0441 23.1759 10.597 22.9584 10.2708 22.9584Z" stroke="#ECECF1" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.45837 9.66675H20.5417" stroke="#ECECF1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.45837 15.7083H15.7084" stroke="#ECECF1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 880 B

View File

@@ -5,7 +5,7 @@ import DocsGPT3 from './assets/cute_docsgpt3.svg';
export default function About() {
return (
<div className="mx-5 grid min-h-screen md:mx-36">
<article className="place-items-left mx-auto my-auto flex w-full max-w-6xl flex-col gap-4 rounded-3xl bg-gray-100 p-6 text-jet lg:p-6 xl:p-10">
<article className="place-items-left mx-auto my-auto flex w-full max-w-6xl flex-col gap-4 rounded-3xl bg-gray-100 p-6 text-jet dark:bg-gun-metal dark:text-bright-gray lg:p-6 xl:p-10">
<div className="flex items-center">
<p className="mr-2 text-3xl">About DocsGPT</p>
<img className="h14 mb-2" src={DocsGPT3} alt="DocsGPT" />
@@ -31,23 +31,34 @@ export default function About() {
</p>
<p className="mt-4 ml-2">
1. Navigate to{' '}
<span className="bg-gray-200 italic"> /application</span> folder
<span className="bg-gray-200 italic dark:bg-outer-space">
{' '}
/application
</span>{' '}
folder
</p>
<p className="mt-4 ml-2">
2. Install dependencies from{' '}
<span className="bg-gray-200 italic">
<span className="bg-gray-200 italic dark:bg-outer-space">
pip install -r requirements.txt
</span>
</p>
<p className="mt-4 ml-2">
3. Prepare a <span className="bg-gray-200 italic">.env</span> file.
Copy <span className="bg-gray-200 italic">.env_sample</span> and
create <span className="bg-gray-200 italic">.env</span> with your
OpenAI API token
3. Prepare a{' '}
<span className="bg-gray-200 italic dark:bg-outer-space">.env</span>{' '}
file. Copy{' '}
<span className="bg-gray-200 italic dark:bg-outer-space">
.env_sample
</span>{' '}
and create{' '}
<span className="bg-gray-200 italic dark:bg-outer-space">.env</span>{' '}
with your OpenAI API token
</p>
<p className="mt-4 ml-2">
4. Run the app with{' '}
<span className="bg-gray-200 italic">python app.py</span>
<span className="bg-gray-200 italic dark:bg-outer-space">
python app.py
</span>
</p>
</div>

View File

@@ -13,9 +13,8 @@ inject();
export default function App() {
const { isMobile } = useMediaQuery();
const [navOpen, setNavOpen] = useState(!isMobile);
return (
<div className="min-h-full min-w-full">
<div className="min-h-full min-w-full dark:bg-raisin-black">
<Navigation navOpen={navOpen} setNavOpen={setNavOpen} />
<div
className={`transition-all duration-200 ${

View File

@@ -1,55 +1,58 @@
import { useMediaQuery } from './hooks';
import { useDarkTheme, useMediaQuery } from './hooks';
import DocsGPT3 from './assets/cute_docsgpt3.svg';
export default function Hero({ className = '' }: { className?: string }) {
// const isMobile = window.innerWidth <= 768;
const { isMobile } = useMediaQuery();
const [isDarkTheme] = useDarkTheme();
return (
<div className={`mt-14 ${isMobile ? 'mb-2' : 'mb-12'}flex flex-col `}>
<div
className={`mt-14 mb-32 flex flex-col text-black-1000 dark:text-bright-gray lg:mt-6`}
>
<div className=" mb-2 flex items-center justify-center sm:mb-10">
<p className="mr-2 text-4xl font-semibold">DocsGPT</p>
<img className="mb-2 h-14" src={DocsGPT3} alt="DocsGPT" />
</div>
{isMobile ? (
<p className="mb-3 text-center leading-6 text-black-1000">
Welcome to <span className="font-bold ">DocsGPT</span>, your technical
<p className="mb-3 text-center leading-6">
Welcome to <span className="font-bold">DocsGPT</span>, your technical
documentation assistant! Start by entering your query in the input
field below, and we&apos;ll provide you with the most relevant
answers.
</p>
) : (
<>
<p className="mb-3 text-center leading-6 text-black-1000">
<p className="mb-3 text-center leading-6">
Welcome to DocsGPT, your technical documentation assistant!
</p>
<p className="mb-3 text-center leading-6 text-black-1000">
<p className="mb-3 text-center leading-6">
Enter a query related to the information in the documentation you
selected to receive
<br /> and we will provide you with the most relevant answers.
</p>
<p className="mb-3 text-center leading-6 text-black-1000">
<p className="mb-3 text-center leading-6">
Start by entering your query in the input field below and we will do
the rest!
</p>
</>
)}
<div
className={`sections ${
isMobile ? '' : 'mt-1'
} flex flex-wrap items-center justify-center gap-2 sm:gap-1 md:gap-0`}
className={`mt-0 flex flex-wrap items-center justify-center gap-2 sm:mt-1 sm:gap-4 md:gap-4 lg:gap-0`}
>
{/* first */}
<div className=" rounded-[50px] bg-gradient-to-l from-[#6EE7B7]/70 via-[#3B82F6] to-[#9333EA]/50 p-1 md:rounded-tr-none md:rounded-br-none">
<div className="h-auto rounded-[50px] bg-gradient-to-l from-[#6EE7B7]/70 via-[#3B82F6] to-[#9333EA]/50 p-1 dark:from-[#D16FF8] dark:via-[#48E6E0] dark:to-[#C85EF6] lg:h-60 lg:rounded-tr-none lg:rounded-br-none">
<div
className={`h-full rounded-[45px] bg-white p-${
className={`h-full rounded-[45px] bg-white dark:bg-dark-charcoal p-${
isMobile ? '3.5' : '6 py-8'
} md:rounded-tr-none md:rounded-br-none`}
} lg:rounded-tr-none lg:rounded-br-none`}
>
{/* Add Mobile check here */}
{isMobile ? (
<div className="flex justify-center">
<img
src="/message-text.svg"
src={
isDarkTheme ? '/message-text-dark.svg' : '/message-text.svg'
}
alt="lock"
className="h-[24px] w-[24px] "
/>
@@ -60,7 +63,9 @@ export default function Hero({ className = '' }: { className?: string }) {
) : (
<>
<img
src="/message-text.svg"
src={
isDarkTheme ? '/message-text-dark.svg' : '/message-text.svg'
}
alt="lock"
className="h-[24px] w-[24px]"
/>
@@ -72,8 +77,8 @@ export default function Hero({ className = '' }: { className?: string }) {
<p
className={
isMobile
? `w-[250px] text-center text-xs text-gray-500`
: `w-[250px] text-xs text-gray-500`
? `w-[250px] text-center text-xs text-gray-500 dark:text-bright-gray`
: `w-[250px] text-xs text-gray-500 dark:text-bright-gray`
}
>
DocsGPT will use your data to answer questions. Whether its
@@ -84,23 +89,31 @@ export default function Hero({ className = '' }: { className?: string }) {
</div>
</div>
{/* second */}
<div className=" rounded-[50px] bg-gradient-to-r from-[#6EE7B7]/70 via-[#3B82F6] to-[#9333EA]/50 p-1 md:rounded-none md:py-1 md:px-0">
<div className="h-auto rounded-[50px] bg-gradient-to-r from-[#6EE7B7]/70 via-[#3B82F6] to-[#9333EA]/50 p-1 dark:from-[#D16FF8] dark:via-[#48E6E0] dark:to-[#C85EF6] lg:h-60 lg:rounded-none lg:py-1 lg:px-0">
<div
className={`rounded-[45px] bg-white p-${
className={`h-full rounded-[45px] bg-white dark:bg-dark-charcoal p-${
isMobile ? '3.5' : '6 py-6'
} md:rounded-none`}
} lg:rounded-none`}
>
{/* Add Mobile check here */}
{isMobile ? (
<div className="flex justify-center">
<img src="/lock.svg" alt="lock" className="h-[24px] w-[24px]" />
<div className="flex justify-center ">
<img
src={isDarkTheme ? '/lock-dark.svg' : '/lock.svg'}
alt="lock"
className="h-[24px] w-[24px]"
/>
<h2 className="mb-0 pl-1 text-lg font-bold">
Secure Data Storage
</h2>
</div>
) : (
<>
<img src="/lock.svg" alt="lock" className="h-[24px] w-[24px]" />
<img
src={isDarkTheme ? '/lock-dark.svg' : '/lock.svg'}
alt="lock"
className="h-[24px] w-[24px]"
/>
<h2 className="mt-2 mb-3 text-lg font-bold">
Secure Data Storage
</h2>
@@ -109,8 +122,8 @@ export default function Hero({ className = '' }: { className?: string }) {
<p
className={
isMobile
? `w-[250px] text-center text-xs text-gray-500`
: `w-[250px] text-xs text-gray-500`
? `w-[250px] text-center text-xs text-gray-500 dark:text-bright-gray`
: `w-[250px] text-xs text-gray-500 dark:text-bright-gray`
}
>
The security of your data is our top priority. DocsGPT ensures the
@@ -121,9 +134,9 @@ export default function Hero({ className = '' }: { className?: string }) {
</div>
</div>
{/* third */}
<div className=" rounded-[50px] bg-gradient-to-l from-[#6EE7B7]/80 via-[#3B82F6] to-[#9333EA]/50 p-1 md:rounded-tl-none md:rounded-bl-none ">
<div className="h-auto rounded-[50px] bg-gradient-to-l from-[#6EE7B7]/70 via-[#3B82F6] to-[#9333EA]/50 p-1 dark:from-[#D16FF8] dark:via-[#48E6E0] dark:to-[#C85EF6] lg:h-60 lg:rounded-tl-none lg:rounded-bl-none ">
<div
className={`firefox rounded-[45px] bg-white p-${
className={`firefox h-full rounded-[45px] bg-white dark:bg-dark-charcoal p-${
isMobile ? '3.5' : '6 px-6 '
} lg:rounded-tl-none lg:rounded-bl-none`}
>
@@ -131,7 +144,11 @@ export default function Hero({ className = '' }: { className?: string }) {
{isMobile ? (
<div className="flex justify-center">
<img
src="/message-programming.svg"
src={
isDarkTheme
? 'message-programming-dark.svg'
: '/message-programming.svg'
}
alt="lock"
className="h-[24px] w-[24px]"
/>
@@ -142,7 +159,11 @@ export default function Hero({ className = '' }: { className?: string }) {
) : (
<>
<img
src="/message-programming.svg"
src={
isDarkTheme
? '/message-programming-dark.svg'
: '/message-programming.svg'
}
alt="lock"
className="h-[24px] w-[24px]"
/>
@@ -154,8 +175,8 @@ export default function Hero({ className = '' }: { className?: string }) {
<p
className={
isMobile
? `w-[250px] text-center text-xs text-gray-500`
: `w-[250px] text-xs text-gray-500`
? `w-[250px] text-center text-xs text-gray-500 dark:text-bright-gray`
: `w-[250px] text-xs text-gray-500 dark:text-bright-gray`
}
>
DocsGPT is built on open source principles, promoting transparency

View File

@@ -1,16 +1,21 @@
import { useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { NavLink, useNavigate } from 'react-router-dom';
import PropTypes from 'prop-types';
import DocsGPT3 from './assets/cute_docsgpt3.svg';
import Documentation from './assets/documentation.svg';
import DocumentationDark from './assets/documentation-dark.svg';
import Discord from './assets/discord.svg';
import Arrow2 from './assets/dropdown-arrow.svg';
import DiscordDark from './assets/discord-dark.svg';
import Expand from './assets/expand.svg';
import Trash from './assets/trash.svg';
import Github from './assets/github.svg';
import GithubDark from './assets/github-dark.svg';
import Hamburger from './assets/hamburger.svg';
import HamburgerDark from './assets/hamburger-dark.svg';
import Info from './assets/info.svg';
import InfoDark from './assets/info-dark.svg';
import SettingGear from './assets/settingGear.svg';
import SettingGearDark from './assets/settingGear-dark.svg';
import Add from './assets/add.svg';
import UploadIcon from './assets/upload.svg';
import { ActiveState } from './models/misc';
@@ -34,12 +39,28 @@ import Upload from './upload/Upload';
import { Doc, getConversations } from './preferences/preferenceApi';
import SelectDocsModal from './preferences/SelectDocsModal';
import ConversationTile from './conversation/ConversationTile';
import { useDarkTheme } from './hooks';
import SourceDropdown from './components/SourceDropdown';
interface NavigationProps {
navOpen: boolean;
setNavOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
const NavImage: React.FC<{
Light: string | undefined;
Dark: string | undefined;
}> = ({ Light, Dark }) => {
return (
<>
<img src={Dark} alt="icon" className="ml-2 hidden w-5 dark:block " />
<img src={Light} alt="icon" className="ml-2 w-5 dark:hidden " />
</>
);
};
NavImage.propTypes = {
Light: PropTypes.string,
Dark: PropTypes.string,
};
export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
const dispatch = useDispatch();
const docs = useSelector(selectSourceDocs);
@@ -47,7 +68,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
const conversations = useSelector(selectConversations);
const conversationId = useSelector(selectConversationId);
const { isMobile } = useMediaQuery();
const [isDarkTheme] = useDarkTheme();
const [isDocsListOpen, setIsDocsListOpen] = useState(false);
const isApiKeySet = useSelector(selectApiKeyStatus);
@@ -63,8 +84,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
const navRef = useRef(null);
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME || 'openai_text-embedding-ada-002';
const navigate = useNavigate();
@@ -73,7 +92,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
fetchConversations();
}
}, [conversations, dispatch]);
async function fetchConversations() {
return await getConversations()
.then((fetchedConversations) => {
@@ -168,12 +186,11 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
useEffect(() => {
setNavOpen(!isMobile);
}, [isMobile]);
return (
<>
{!navOpen && (
<button
className="duration-25 absolute sticky top-3 left-3 z-20 hidden transition-all md:block"
className="duration-25 absolute top-3 left-3 z-20 hidden transition-all md:block"
onClick={() => {
setNavOpen(!navOpen);
}}
@@ -191,10 +208,10 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
ref={navRef}
className={`${
!navOpen && '-ml-96 md:-ml-[18rem]'
} duration-20 fixed top-0 z-20 flex h-full w-72 flex-col border-r-2 bg-white transition-all`}
} duration-20 fixed top-0 z-20 flex h-full w-72 flex-col border-r-[1px] border-b-0 bg-white transition-all dark:border-r-purple-taupe dark:bg-chinese-black dark:text-white`}
>
<div
className={'visible mt-2 flex h-16 w-full justify-between md:h-12'}
className={'visible mt-2 flex h-[6vh] w-full justify-between md:h-12'}
>
<div className="my-auto mx-4 flex cursor-pointer gap-1.5">
<img className="mb-2 h-10" src={DocsGPT3} alt="" />
@@ -215,182 +232,142 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
/>
</button>
</div>
<div className="mb-auto">
<NavLink
to={'/'}
onClick={() => {
dispatch(setConversation([]));
dispatch(
updateConversationId({
query: { conversationId: null },
}),
);
}}
className={({ isActive }) =>
`${
isActive ? 'bg-gray-3000' : ''
} group mx-4 mt-4 flex cursor-pointer gap-2.5 rounded-3xl border border-silver p-3 hover:border-rainy-gray hover:bg-gray-3000`
}
>
<img
src={Add}
alt="new"
className="opacity-80 group-hover:opacity-100"
/>
<p className=" text-sm text-dove-gray group-hover:text-neutral-600">
New Chat
</p>
</NavLink>
<NavLink
to={'/'}
onClick={() => {
dispatch(setConversation([]));
dispatch(
updateConversationId({
query: { conversationId: null },
}),
);
}}
className={({ isActive }) =>
`${
isActive ? 'bg-gray-3000 dark:bg-transparent' : ''
} group sticky mx-4 mt-4 flex cursor-pointer gap-2.5 rounded-3xl border border-silver p-3 hover:border-rainy-gray hover:bg-gray-3000 dark:border-purple-taupe dark:text-white dark:hover:bg-transparent`
}
>
<img
src={Add}
alt="new"
className="opacity-80 group-hover:opacity-100"
/>
<p className=" text-sm text-dove-gray group-hover:text-neutral-600 dark:text-chinese-silver dark:group-hover:text-bright-gray">
New Chat
</p>
</NavLink>
<div className="mb-auto h-[56vh] overflow-y-auto overflow-x-hidden dark:text-white">
{conversations && (
<div className="conversations-container max-h-[25rem] overflow-y-auto">
<div>
<p className="ml-6 mt-3 text-sm font-semibold">Chats</p>
{conversations?.map((conversation) => (
<ConversationTile
key={conversation.id}
conversation={conversation}
selectConversation={(id) => handleConversationClick(id)}
onDeleteConversation={(id) => handleDeleteConversation(id)}
onSave={(conversation) =>
updateConversationName(conversation)
}
/>
))}
<div className="conversations-container">
{conversations?.map((conversation) => (
<ConversationTile
key={conversation.id}
conversation={conversation}
selectConversation={(id) => handleConversationClick(id)}
onDeleteConversation={(id) => handleDeleteConversation(id)}
onSave={(conversation) =>
updateConversationName(conversation)
}
/>
))}
</div>
</div>
)}
</div>
<div className="flex flex-col-reverse border-b-2">
<div className="relative my-4 flex gap-2 px-2">
<div
className="flex h-12 w-5/6 cursor-pointer justify-between rounded-3xl border-2 bg-white"
onClick={() => setIsDocsListOpen(!isDocsListOpen)}
>
{selectedDocs && (
<p className="my-3 mx-4 overflow-hidden text-ellipsis whitespace-nowrap">
{selectedDocs.name} {selectedDocs.version}
</p>
)}
<img
src={Arrow2}
alt="arrow"
className={`${
!isDocsListOpen ? 'rotate-0' : 'rotate-180'
} ml-auto mr-3 w-3 transition-all`}
<div className="flex h-auto flex-col justify-end text-eerie-black dark:text-white">
<div className="flex flex-col-reverse border-b-[1px] dark:border-b-purple-taupe">
<div className="relative my-4 flex gap-2 px-2">
<SourceDropdown
options={docs}
selectedDocs={selectedDocs}
setSelectedDocs={setSelectedDocs}
isDocsListOpen={isDocsListOpen}
setIsDocsListOpen={setIsDocsListOpen}
handleDeleteClick={handleDeleteClick}
/>
<img
className="mt-2 h-9 w-9 hover:cursor-pointer"
src={UploadIcon}
onClick={() => setUploadModalState('ACTIVE')}
></img>
</div>
<img
className="mt-2 h-9 w-9 hover:cursor-pointer"
src={UploadIcon}
onClick={() => setUploadModalState('ACTIVE')}
></img>
{isDocsListOpen && (
<div className="absolute top-12 left-0 right-6 z-10 ml-2 mr-4 max-h-52 overflow-y-scroll bg-white shadow-lg">
{docs ? (
docs.map((doc, index) => {
if (doc.model === embeddingsName) {
return (
<div
key={index}
onClick={() => {
dispatch(setSelectedDocs(doc));
setIsDocsListOpen(false);
}}
className="flex h-10 w-full cursor-pointer items-center justify-between border-x-2 border-b-2 hover:bg-gray-100"
>
<p className="ml-5 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3">
{doc.name} {doc.version}
</p>
{doc.location === 'local' && (
<img
src={Trash}
alt="Delete"
className="mr-4 h-4 w-4 cursor-pointer hover:opacity-50"
id={`img-${index}`}
onClick={(event) => {
event.stopPropagation();
handleDeleteClick(index, doc);
}}
/>
)}
</div>
);
}
})
) : (
<div className="h-10 w-full cursor-pointer border-x-2 border-b-2 hover:bg-gray-100">
<p className="ml-5 py-3">No default documentation.</p>
</div>
)}
</div>
)}
<p className="ml-6 mt-3 text-sm font-semibold">Source Docs</p>
</div>
<div className="flex flex-col gap-2 border-b-[1px] py-2 dark:border-b-purple-taupe">
<NavLink
to="/settings"
className={({ isActive }) =>
`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' : ''
}`
}
>
<NavImage Light={SettingGear} Dark={SettingGearDark} />
<p className="my-auto text-sm text-eerie-black dark:text-white">
Settings
</p>
</NavLink>
</div>
<p className="ml-6 mt-3 text-sm font-semibold">Source Docs</p>
</div>
<div className="flex flex-col gap-2 border-b-2 py-2">
<NavLink
to="/settings"
className={({ isActive }) =>
`my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 ${
isActive ? 'bg-gray-3000' : ''
}`
}
>
<img src={SettingGear} alt="info" className="ml-2 w-5 opacity-60" />
<p className="my-auto text-sm text-eerie-black">Settings</p>
</NavLink>
</div>
<div className="flex flex-col gap-2 border-b-2 py-2">
<NavLink
to="/about"
className={({ isActive }) =>
`my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 ${
isActive ? 'bg-gray-3000' : ''
}`
}
>
<img src={Info} alt="info" className="ml-2 w-5" />
<p className="my-auto text-sm text-eerie-black">About</p>
</NavLink>
<div className="flex flex-col gap-2 border-b-[1.5px] py-2 dark:border-b-purple-taupe">
<NavLink
to="/about"
className={({ isActive }) =>
`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' : ''
}`
}
>
<NavImage Light={Info} Dark={InfoDark} />
<p className="my-auto text-sm">About</p>
</NavLink>
<a
href="https://docs.docsgpt.co.uk/"
target="_blank"
rel="noreferrer"
className="my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100"
>
<img src={Documentation} alt="documentation" className="ml-2 w-5" />
<p className="my-auto text-sm text-eerie-black">Documentation</p>
</a>
<a
href="https://discord.gg/WHJdfbQDR4"
target="_blank"
rel="noreferrer"
className="my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100"
>
<img src={Discord} alt="link" className="ml-2 w-5" />
<p className="my-auto text-sm text-eerie-black">
Visit our Discord
</p>
</a>
<a
href="https://docs.docsgpt.co.uk/"
target="_blank"
rel="noreferrer"
className="my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-purple-taupe"
>
<NavImage Light={Documentation} Dark={DocumentationDark} />
<p className="my-auto text-sm ">Documentation</p>
</a>
<a
href="https://discord.gg/WHJdfbQDR4"
target="_blank"
rel="noreferrer"
className="my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-purple-taupe"
>
<NavImage Light={Discord} Dark={DiscordDark} />
{/* <img src={isDarkTheme ? DiscordDark : Discord} alt="discord-link" className="ml-2 w-5" /> */}
<p className="my-auto text-sm">Visit our Discord</p>
</a>
<a
href="https://github.com/arc53/DocsGPT"
target="_blank"
rel="noreferrer"
className="my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100"
>
<img src={Github} alt="link" className="ml-2 w-5" />
<p className="my-auto text-sm text-eerie-black">Visit our Github</p>
</a>
<a
href="https://github.com/arc53/DocsGPT"
target="_blank"
rel="noreferrer"
className="mx-4 mt-auto flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-purple-taupe"
>
<NavImage Light={Github} Dark={GithubDark} />
<p className="my-auto text-sm">Visit our Github</p>
</a>
</div>
</div>
</div>
<div className="fixed z-10 h-16 w-full border-b-2 bg-gray-50 md:hidden">
<div className="fixed z-10 h-16 w-full border-b-2 bg-gray-50 dark:border-b-purple-taupe dark:bg-chinese-black md:hidden">
<button
className="mt-5 ml-6 h-6 w-6 md:hidden"
onClick={() => setNavOpen(true)}
>
<img src={Hamburger} alt="menu toggle" className="w-7" />
<img
src={isDarkTheme ? HamburgerDark : Hamburger}
alt="menu toggle"
className="w-7"
/>
</button>
</div>
<SelectDocsModal

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,3 @@
<svg width="14" height="17" stroke-width="1.15" viewBox="0 0 14 17" >
<svg width="13" height="16" stroke-width="1.15" viewBox="0 0 14 17" >
<path d="M13.8013 5.01282L8.80645 0.191795C8.67953 0.0691399 8.50734 0.000152609 8.32774 0H6.09677C5.43801 0 4.80623 0.252586 4.34041 0.702193C3.8746 1.1518 3.6129 1.7616 3.6129 2.39744V3.48718H2.48387C1.82511 3.48718 1.19332 3.73977 0.727509 4.18937C0.261693 4.63898 0 5.24878 0 5.88462V14.6026C0 15.2384 0.261693 15.8482 0.727509 16.2978C1.19332 16.7474 1.82511 17 2.48387 17H8.80645C9.46521 17 10.097 16.7474 10.5628 16.2978C11.0286 15.8482 11.2903 15.2384 11.2903 14.6026V13.5128H11.5161C12.1749 13.5128 12.8067 13.2602 13.2725 12.8106C13.7383 12.361 14 11.7512 14 11.1154V5.44872C13.9929 5.28447 13.9219 5.12884 13.8013 5.01282ZM9.03226 2.23179L11.6877 4.79487H9.03226V2.23179ZM9.93548 14.6026C9.93548 14.8916 9.81653 15.1688 9.6048 15.3731C9.39306 15.5775 9.10589 15.6923 8.80645 15.6923H2.48387C2.18443 15.6923 1.89726 15.5775 1.68552 15.3731C1.47379 15.1688 1.35484 14.8916 1.35484 14.6026V5.88462C1.35484 5.5956 1.47379 5.31842 1.68552 5.11405C1.89726 4.90968 2.18443 4.79487 2.48387 4.79487H3.6129V11.1154C3.6129 11.7512 3.8746 12.361 4.34041 12.8106C4.80623 13.2602 5.43801 13.5128 6.09677 13.5128H9.93548V14.6026ZM11.5161 12.2051H6.09677C5.79734 12.2051 5.51016 12.0903 5.29843 11.886C5.08669 11.6816 4.96774 11.4044 4.96774 11.1154V2.39744C4.96774 2.10842 5.08669 1.83124 5.29843 1.62687C5.51016 1.4225 5.79734 1.30769 6.09677 1.30769H7.67742V5.44872C7.67976 5.62143 7.75188 5.78643 7.87842 5.90856C8.00496 6.03069 8.1759 6.10031 8.35484 6.10256H12.6452V11.1154C12.6452 11.4044 12.5262 11.6816 12.3145 11.886C12.1027 12.0903 11.8156 12.2051 11.5161 12.2051Z" fill="#949494"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="18" viewBox="0 0 22 18" fill="none">
<path d="M18.7359 2.29769C17.4059 1.66546 15.9659 1.20658 14.4659 0.941453C14.4528 0.941024 14.4397 0.943541 14.4276 0.948827C14.4155 0.954112 14.4047 0.962038 14.3959 0.972045C14.2159 1.30856 14.0059 1.74704 13.8659 2.08355C12.2749 1.83882 10.6569 1.83882 9.06594 2.08355C8.92594 1.73684 8.71594 1.30856 8.52594 0.972045C8.51594 0.951651 8.48594 0.941453 8.45594 0.941453C6.95594 1.20658 5.52594 1.66546 4.18594 2.29769C4.17594 2.29769 4.16594 2.30789 4.15594 2.31809C1.43594 6.46839 0.685937 10.5065 1.05594 14.5039C1.05594 14.5243 1.06594 14.5447 1.08594 14.5548C2.88594 15.9009 4.61594 16.7167 6.32594 17.2571C6.35594 17.2673 6.38594 17.2571 6.39594 17.2367C6.79594 16.6759 7.15594 16.0844 7.46594 15.4624C7.48594 15.4216 7.46594 15.3808 7.42594 15.3706C6.85594 15.1463 6.31594 14.8812 5.78594 14.5752C5.74594 14.5549 5.74594 14.4937 5.77594 14.4631C5.88594 14.3815 5.99594 14.2897 6.10594 14.2081C6.12594 14.1877 6.15594 14.1877 6.17594 14.1979C9.61594 15.7989 13.3259 15.7989 16.7259 14.1979C16.7459 14.1877 16.7759 14.1877 16.7959 14.2081C16.9059 14.2999 17.0159 14.3815 17.1259 14.4733C17.1659 14.5039 17.1659 14.565 17.1159 14.5854C16.5959 14.9016 16.0459 15.1565 15.4759 15.3808C15.4359 15.391 15.4259 15.442 15.4359 15.4726C15.7559 16.0946 16.1159 16.6861 16.5059 17.2469C16.5359 17.2571 16.5659 17.2673 16.5959 17.2571C18.3159 16.7167 20.0459 15.9009 21.8459 14.5548C21.8659 14.5447 21.8759 14.5243 21.8759 14.5039C22.3159 9.88449 21.1459 5.87695 18.7759 2.31809C18.7659 2.30789 18.7559 2.29769 18.7359 2.29769ZM7.98594 12.0667C6.95594 12.0667 6.09594 11.098 6.09594 9.90488C6.09594 8.7118 6.93594 7.74305 7.98594 7.74305C9.04594 7.74305 9.88594 8.72199 9.87594 9.90488C9.87594 11.098 9.03594 12.0667 7.98594 12.0667ZM14.9559 12.0667C13.9259 12.0667 13.0659 11.098 13.0659 9.90488C13.0659 8.7118 13.9059 7.74305 14.9559 7.74305C16.0159 7.74305 16.8559 8.72199 16.8459 9.90488C16.8459 11.098 16.0159 12.0667 14.9559 12.0667Z" fill="#949494"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="current" xmlns="http://www.w3.org/2000/svg">
<path d="M6.37776 10.1001V12.9C6.37776 13.457 6.599 13.9911 6.99282 14.3849C7.38664 14.7788 7.92077 15 8.47772 15L11.2777 8.70011V1.00025H3.38181C3.04419 0.996436 2.71656 1.11477 2.45929 1.33344C2.20203 1.55212 2.03246 1.8564 1.98184 2.19023L1.01585 8.49012C0.985398 8.69076 0.998931 8.89563 1.05551 9.09053C1.1121 9.28543 1.21038 9.46569 1.34355 9.61884C1.47671 9.77198 1.64159 9.89434 1.82674 9.97744C2.01189 10.0605 2.2129 10.1024 2.41583 10.1001H6.37776ZM11.2777 1.00025H13.1466C13.5428 0.993247 13.9277 1.13195 14.2284 1.39002C14.5291 1.64809 14.7245 2.00758 14.7776 2.40023V7.30014C14.7245 7.69279 14.5291 8.05227 14.2284 8.31035C13.9277 8.56842 13.5428 8.70712 13.1466 8.70011H11.2777" fill="white"/>
<svg width="14" height="14" viewBox="0 0 16 16" fill="current" xmlns="http://www.w3.org/2000/svg">
<path d="M6.37776 10.1001V12.9C6.37776 13.457 6.599 13.9911 6.99282 14.3849C7.38664 14.7788 7.92077 15 8.47772 15L11.2777 8.70011V1.00025H3.38181C3.04419 0.996436 2.71656 1.11477 2.45929 1.33344C2.20203 1.55212 2.03246 1.8564 1.98184 2.19023L1.01585 8.49012C0.985398 8.69076 0.998931 8.89563 1.05551 9.09053C1.1121 9.28543 1.21038 9.46569 1.34355 9.61884C1.47671 9.77198 1.64159 9.89434 1.82674 9.97744C2.01189 10.0605 2.2129 10.1024 2.41583 10.1001H6.37776ZM11.2777 1.00025H13.1466C13.5428 0.993247 13.9277 1.13195 14.2284 1.39002C14.5291 1.64809 14.7245 2.00758 14.7776 2.40023V7.30014C14.7245 7.69279 14.5291 8.05227 14.2284 8.31035C13.9277 8.56842 13.5428 8.70712 13.1466 8.70011H11.2777" fill="none"/>
<path d="M11.2777 8.70011L8.47772 15C7.92077 15 7.38664 14.7788 6.99282 14.3849C6.599 13.9911 6.37776 13.457 6.37776 12.9V10.1001H2.41583C2.2129 10.1024 2.01189 10.0605 1.82674 9.97744C1.64159 9.89434 1.47671 9.77198 1.34355 9.61884C1.21038 9.46569 1.1121 9.28543 1.05551 9.09053C0.998931 8.89563 0.985398 8.69076 1.01585 8.49012L1.98184 2.19023C2.03246 1.8564 2.20203 1.55212 2.45929 1.33344C2.71656 1.11477 3.04419 0.996436 3.38181 1.00025H11.2777M11.2777 8.70011V1.00025M11.2777 8.70011H13.1466C13.5428 8.70712 13.9277 8.56842 14.2284 8.31035C14.5291 8.05227 14.7245 7.69279 14.7776 7.30014V2.40023C14.7245 2.00758 14.5291 1.64809 14.2284 1.39002C13.9277 1.13195 13.5428 0.993247 13.1466 1.00025H11.2777" stroke="current" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0,0,256,256">
<g transform="translate(-19.2,-19.2) scale(1.15,1.15)"><g fill="#949494" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(10.66667,10.66667)"><path d="M13.172,2h-7.172c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-11.172c0,-0.53 -0.211,-1.039 -0.586,-1.414l-4.828,-4.828c-0.375,-0.375 -0.884,-0.586 -1.414,-0.586zM15,18h-6c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0c0,0.552 -0.448,1 -1,1zM15,14h-6c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0c0,0.552 -0.448,1 -1,1zM13,9v-5.5l5.5,5.5z"></path></g></g></g>
</svg>

After

Width:  |  Height:  |  Size: 933 B

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0,0,256,256">
<g transform="translate(-19.2,-19.2) scale(1.15,1.15)"><g fill="black" fill-opacity="0.5" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(10.66667,10.66667)"><path d="M13.172,2h-7.172c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-11.172c0,-0.53 -0.211,-1.039 -0.586,-1.414l-4.828,-4.828c-0.375,-0.375 -0.884,-0.586 -1.414,-0.586zM15,18h-6c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0c0,0.552 -0.448,1 -1,1zM15,14h-6c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0c0,0.552 -0.448,1 -1,1zM13,9v-5.5l5.5,5.5z"></path></g></g></g>
<g transform="translate(-19.2,-19.2) scale(1.15,1.15)"><g fill="black" fill-opacity="0.54" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(10.66667,10.66667)"><path d="M13.172,2h-7.172c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-11.172c0,-0.53 -0.211,-1.039 -0.586,-1.414l-4.828,-4.828c-0.375,-0.375 -0.884,-0.586 -1.414,-0.586zM15,18h-6c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0c0,0.552 -0.448,1 -1,1zM15,14h-6c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0c0,0.552 -0.448,1 -1,1zM13,9v-5.5l5.5,5.5z"></path></g></g></g>
</svg>

Before

Width:  |  Height:  |  Size: 950 B

After

Width:  |  Height:  |  Size: 951 B

View File

@@ -1,3 +1,3 @@
<svg width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0L5 5L10 0H0Z" fill="black" fill-opacity="0.54"/>
<path d="M0 0L5 5L10 0H0Z" fill="gray" fill-opacity="0.80"/>
</svg>

Before

Width:  |  Height:  |  Size: 163 B

After

Width:  |  Height:  |  Size: 162 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="22" viewBox="0 0 21 22" fill="none">
<path d="M19.8155 2.09139C19.0825 1.34392 18.2005 0.970703 17.1685 0.970703H4.68155C3.64955 0.970703 2.76755 1.34392 2.03455 2.09139C1.30155 2.83885 0.935547 3.73825 0.935547 4.79061V17.524C0.935547 18.5763 1.30155 19.4757 2.03455 20.2232C2.76755 20.9707 3.64955 21.3439 4.68155 21.3439H7.59555C7.78555 21.3439 7.92855 21.3368 8.02455 21.3235C8.13624 21.3007 8.23705 21.2399 8.31055 21.1512C8.40555 21.0492 8.45355 20.9013 8.45355 20.7076L8.44655 19.8051C8.44255 19.23 8.44055 18.7752 8.44055 18.4387L8.14055 18.4917C7.95055 18.5274 7.71055 18.5427 7.41955 18.5386C7.11624 18.5329 6.81389 18.5019 6.51555 18.4458C6.19795 18.386 5.89897 18.2497 5.64355 18.0481C5.37604 17.8418 5.17652 17.5572 5.07155 17.2323L4.94155 16.9264C4.83198 16.6851 4.69432 16.4581 4.53155 16.2503C4.34555 16.0025 4.15655 15.8353 3.96555 15.7466L3.87555 15.6803C3.81279 15.6345 3.75571 15.5811 3.70555 15.5212C3.65764 15.4657 3.6182 15.4031 3.58855 15.3356C3.56255 15.2734 3.58455 15.2224 3.65355 15.1827C3.72355 15.1419 3.84855 15.1225 4.03155 15.1225L4.29155 15.1633C4.46455 15.198 4.67955 15.304 4.93455 15.4804C5.19261 15.6598 5.40818 15.8957 5.56555 16.1708C5.76555 16.5328 6.00555 16.8091 6.28755 16.9998C6.56955 17.1895 6.85355 17.2854 7.13955 17.2854C7.42555 17.2854 7.67255 17.2629 7.88155 17.2191C8.08366 17.1765 8.28005 17.1094 8.46655 17.0192C8.54455 16.4278 8.75655 15.9709 9.10355 15.6528C8.65392 15.6078 8.2083 15.5281 7.77055 15.4142C7.34334 15.2945 6.93249 15.1208 6.54755 14.8972C6.14479 14.6736 5.78905 14.3714 5.50055 14.008C5.22355 13.6541 4.99555 13.1901 4.81755 12.616C4.64055 12.0409 4.55155 11.377 4.55155 10.6255C4.55155 9.55581 4.89355 8.64519 5.57855 7.89263C5.25855 7.08908 5.28855 6.18662 5.66955 5.18831C5.92155 5.10775 6.29455 5.16791 6.78855 5.36676C7.28255 5.56561 7.64455 5.7359 7.87455 5.87662C8.10455 6.01939 8.28855 6.13869 8.42755 6.23557C9.24052 6.00486 10.0807 5.88889 10.9245 5.8909C11.7835 5.8909 12.6155 6.00613 13.4225 6.23557L13.9165 5.91741C14.2965 5.68476 14.6973 5.48946 15.1135 5.33413C15.5735 5.15669 15.9235 5.10877 16.1675 5.18831C16.5575 6.18764 16.5915 7.08908 16.2705 7.89365C16.9555 8.64519 17.2985 9.55581 17.2985 10.6265C17.2985 11.3781 17.2095 12.044 17.0315 12.6221C16.8545 13.2013 16.6245 13.6653 16.3425 14.0151C16.049 14.3739 15.6917 14.6731 15.2895 14.8972C14.8695 15.1358 14.4615 15.3081 14.0665 15.4142C13.6288 15.5284 13.1832 15.6085 12.7335 15.6538C13.1835 16.0515 13.4095 16.6786 13.4095 17.5362V20.7076C13.4095 20.8575 13.4305 20.9788 13.4745 21.0716C13.4948 21.1163 13.5236 21.1564 13.5593 21.1895C13.5951 21.2226 13.637 21.2481 13.6825 21.2643C13.7785 21.299 13.8625 21.3215 13.9365 21.3296C14.0105 21.3398 14.1165 21.3429 14.2545 21.3429H17.1685C18.2005 21.3429 19.0825 20.9696 19.8155 20.2222C20.5475 19.4757 20.9145 18.5753 20.9145 17.523V4.79061C20.9145 3.73825 20.5485 2.83885 19.8155 2.09139Z" fill="#949494"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,3 @@
<svg width="600" height="450" viewBox="0 0 600 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25 25H575M25 225H575M25 425H575" stroke="#949494" stroke-opacity="0.54" stroke-width="50" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 256 B

Some files were not shown because too many files have changed in this diff Show More