aak007 commited on
Commit
45b17a5
ยท
0 Parent(s):

Initial commit for Hugging Face

Browse files
Files changed (17) hide show
  1. .dockerignore +11 -0
  2. .github/workflows/cicd.yaml +58 -0
  3. .gitignore +212 -0
  4. Dockerfile +12 -0
  5. LICENSE +201 -0
  6. README.md +218 -0
  7. app.py +141 -0
  8. requirements.txt +12 -0
  9. research/trials.ipynb +0 -0
  10. setup.py +10 -0
  11. src/__init__.py +0 -0
  12. src/helper.py +202 -0
  13. src/prompt.py +8 -0
  14. static/style.css +360 -0
  15. store_index.py +75 -0
  16. template.sh +16 -0
  17. templates/chat.html +143 -0
.dockerignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ .gitignore
3
+ .venv
4
+ __pycache__
5
+ *.pyc
6
+ *.pyo
7
+ *.pyd
8
+ *.log
9
+ .env
10
+ research/
11
+ *.ipynb
.github/workflows/cicd.yaml ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Deploy Application Docker Image to EC2 instance
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+
8
+ jobs:
9
+ Continuous-Integration:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v2
15
+
16
+ - name: Configure AWS credentials
17
+ uses: aws-actions/configure-aws-credentials@v1
18
+ with:
19
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
20
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
21
+ aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
22
+
23
+ - name: Login to Amazon ECR
24
+ id: login-ecr
25
+ uses: aws-actions/amazon-ecr-login@v1
26
+
27
+ - name: Build, tag, and push image to Amazon ECR
28
+ id: build-image
29
+ env:
30
+ ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
31
+ ECR_REPOSITORY: ${{ secrets.ECR_REPO }}
32
+ IMAGE_TAG: latest
33
+ run: |
34
+ docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
35
+ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
36
+ echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
37
+
38
+ Continuous-Deployment:
39
+ needs: Continuous-Integration
40
+ runs-on: self-hosted
41
+ steps:
42
+ - name: Checkout
43
+ uses: actions/checkout@v3
44
+
45
+ - name: Configure AWS credentials
46
+ uses: aws-actions/configure-aws-credentials@v1
47
+ with:
48
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
49
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
50
+ aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
51
+
52
+ - name: Login to Amazon ECR
53
+ id: login-ecr
54
+ uses: aws-actions/amazon-ecr-login@v1
55
+
56
+ - name: Run Docker Image to serve users
57
+ run: |
58
+ docker run -d -e AWS_ACCESS_KEY_ID="${{ secrets.AWS_ACCESS_KEY_ID }}" -e AWS_SECRET_ACCESS_KEY="${{ secrets.AWS_SECRET_ACCESS_KEY }}" -e AWS_DEFAULT_REGION="${{ secrets.AWS_DEFAULT_REGION }}" -e PINECONE_API_KEY="${{ secrets.PINECONE_API_KEY }}" -e OPENAI_API_KEY="${{ secrets.OPENAI_API_KEY }}" -p 8080:8080 "${{ steps.login-ecr.outputs.registry }}"/"${{ secrets.ECR_REPO }}":latest
.gitignore ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+ .env
6
+
7
+ # C extensions
8
+ *.so
9
+
10
+ # Distribution / packaging
11
+ .Python
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py.cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ #Pipfile.lock
97
+
98
+ # UV
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ #uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ #poetry.lock
110
+ #poetry.toml
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
115
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
116
+ #pdm.lock
117
+ #pdm.toml
118
+ .pdm-python
119
+ .pdm-build/
120
+
121
+ # pixi
122
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
123
+ #pixi.lock
124
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
125
+ # in the .venv directory. It is recommended not to include this directory in version control.
126
+ .pixi
127
+
128
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
129
+ __pypackages__/
130
+
131
+ # data
132
+ data/
133
+ *.pdf stuff
134
+
135
+ # Celery stuff
136
+ celerybeat-schedule
137
+ celerybeat.pid
138
+
139
+ # SageMath parsed files
140
+ *.sage.py
141
+
142
+ # Environments
143
+ .env
144
+ .envrc
145
+ .venv
146
+ env/
147
+ venv/
148
+ ENV/
149
+ env.bak/
150
+ venv.bak/
151
+
152
+ # Spyder project settings
153
+ .spyderproject
154
+ .spyproject
155
+
156
+ # Rope project settings
157
+ .ropeproject
158
+
159
+ # mkdocs documentation
160
+ /site
161
+
162
+ # mypy
163
+ .mypy_cache/
164
+ .dmypy.json
165
+ dmypy.json
166
+
167
+ # Pyre type checker
168
+ .pyre/
169
+
170
+ # pytype static type analyzer
171
+ .pytype/
172
+
173
+ # Cython debug symbols
174
+ cython_debug/
175
+
176
+ # PyCharm
177
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
178
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
179
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
180
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
181
+ #.idea/
182
+
183
+ # Abstra
184
+ # Abstra is an AI-powered process automation framework.
185
+ # Ignore directories containing user credentials, local state, and settings.
186
+ # Learn more at https://abstra.io/docs
187
+ .abstra/
188
+
189
+ # Visual Studio Code
190
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
191
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
192
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
193
+ # you could uncomment the following to ignore the entire vscode folder
194
+ # .vscode/
195
+
196
+ # Ruff stuff:
197
+ .ruff_cache/
198
+
199
+ # PyPI configuration file
200
+ .pypirc
201
+
202
+ # Cursor
203
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
204
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
205
+ # refer to https://docs.cursor.com/context/ignore-files
206
+ .cursorignore
207
+ .cursorindexingignore
208
+
209
+ # Marimo
210
+ marimo/_static/
211
+ marimo/_lsp/
212
+ __marimo__/
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . /app
6
+
7
+ RUN pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu && \
8
+ pip install --no-cache-dir -r requirements.txt
9
+
10
+ EXPOSE 7860
11
+
12
+ CMD ["sh", "-c", "gunicorn --bind 0.0.0.0:${PORT:-7860} --timeout 120 app:app"]
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Student Support Chatbot
3
+ emoji: ๐ŸŽ“
4
+ colorFrom: purple
5
+ colorTo: indigo
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ # ๐ŸŽ“ Student Support Chatbot
11
+
12
+ A fully deployed AI-powered RAG (Retrieval-Augmented Generation) chatbot that answers student queries about admissions, fees, courses, campus life, and more. Built with LangChain, Google Gemini, Pinecone, and Flask.
13
+
14
+ ---
15
+
16
+ ## ๐Ÿ—๏ธ Architecture
17
+
18
+ ```
19
+ Student Question
20
+ โ†“
21
+ HuggingFace Embeddings (all-MiniLM-L6-v2)
22
+ โ†“
23
+ Pinecone Vector DB (semantic search โ†’ top 3 results)
24
+ โ†“
25
+ Google Gemini LLM (generates final answer)
26
+ โ†“
27
+ Flask Web App (chat interface)
28
+ ```
29
+
30
+ **Dataset**: [`bot-remains/student-assistance-chatbot`](https://huggingface.co/datasets/bot-remains/student-assistance-chatbot) โ€” 217 student Q&A pairs covering admissions, eligibility, fees, academics, and campus life.
31
+
32
+ ---
33
+
34
+ ## ๐Ÿ› ๏ธ Tech Stack
35
+
36
+ | Layer | Technology |
37
+ | --------------- | ------------------------------------ |
38
+ | LLM | Google Gemini 2.5 Flash |
39
+ | Embeddings | HuggingFace `all-MiniLM-L6-v2` |
40
+ | Vector Database | Pinecone |
41
+ | Framework | LangChain |
42
+ | Web App | Flask |
43
+ | Deployment | AWS App Runner + Amazon ECR / Docker |
44
+
45
+ ---
46
+
47
+ ## ๐Ÿš€ How to Run Locally
48
+
49
+ ### Step 1: Clone the repository
50
+
51
+ ```bash
52
+ git clone https://github.com/aak007/Build-a-Complete-Medical-Chatbot-with-LLMs-LangChain-Pinecone-Flask-AWS.git
53
+ cd Build-a-Complete-Medical-Chatbot-with-LLMs-LangChain-Pinecone-Flask-AWS
54
+ ```
55
+
56
+ ### Step 2: Create and activate conda environment
57
+
58
+ ```bash
59
+ conda create -n medibot python=3.10 -y
60
+ conda activate medibot
61
+ ```
62
+
63
+ ### Step 3: Install dependencies
64
+
65
+ ```bash
66
+ pip install -r requirements.txt
67
+ ```
68
+
69
+ ### Step 4: Set up environment variables
70
+
71
+ Create a `.env` file in the root directory:
72
+
73
+ ```ini
74
+ PINECONE_API_KEY=your_pinecone_api_key
75
+ GOOGLE_API_KEY=your_gemini_api_key
76
+
77
+ # Optional: choose your index name
78
+ PINECONE_INDEX_NAME=student-chatbot
79
+
80
+ # Dataset source options: hf | local | pdf
81
+ DATA_SOURCE=hf
82
+
83
+ # If DATA_SOURCE=hf
84
+ HF_DATASET_NAME=bot-remains/student-assistance-chatbot
85
+
86
+ # If DATA_SOURCE=local (csv/json/jsonl/txt/md)
87
+ LOCAL_DATASET_PATH=data/my_dataset.csv
88
+ TEXT_COLUMNS=question,answer
89
+
90
+ # If DATA_SOURCE=pdf (single file or folder)
91
+ # LOCAL_DATASET_PATH=data/student_buddy_qa.pdf
92
+ ```
93
+
94
+ ### Step 5: Index the dataset into Pinecone _(Run only once)_
95
+
96
+ ```bash
97
+ python store_index.py
98
+ ```
99
+
100
+ This downloads the student Q&A dataset from HuggingFace and stores it as vector embeddings in your Pinecone `student-chatbot` index.
101
+
102
+ ### Use Your Own Dataset
103
+
104
+ You can replace the default dataset with your own data and re-index it.
105
+
106
+ 1. Put your file inside the project, for example `data/my_dataset.csv`.
107
+ 2. Update `.env`:
108
+
109
+ ```ini
110
+ DATA_SOURCE=local
111
+ LOCAL_DATASET_PATH=data/my_dataset.csv
112
+ TEXT_COLUMNS=question,answer
113
+ PINECONE_INDEX_NAME=my-student-bot-index
114
+ ```
115
+
116
+ 3. Rebuild vectors:
117
+
118
+ ```bash
119
+ python store_index.py
120
+ ```
121
+
122
+ 4. Start app:
123
+
124
+ ```bash
125
+ python app.py
126
+ ```
127
+
128
+ Supported local file formats:
129
+
130
+ - `csv` (recommended)
131
+ - `json` (array of objects)
132
+ - `jsonl` (one JSON object per line)
133
+ - `txt` / `md` (split by blank lines)
134
+
135
+ For PDF knowledge bases:
136
+
137
+ ```ini
138
+ DATA_SOURCE=pdf
139
+ LOCAL_DATASET_PATH=data/student_buddy_qa.pdf
140
+ PINECONE_INDEX_NAME=student-buddy-index
141
+ ```
142
+
143
+ Then run:
144
+
145
+ ```bash
146
+ python store_index.py
147
+ python app.py
148
+ ```
149
+
150
+ Example CSV schema:
151
+
152
+ ```csv
153
+ question,answer,category
154
+ What is eligibility for B.Tech?,Candidates must pass 10+2 with PCM and required cutoff,admissions
155
+ What are hostel fees?,Hostel fees vary by room type and campus policy,fees
156
+ ```
157
+
158
+ ### Step 6: Start the app
159
+
160
+ ```bash
161
+ python app.py
162
+ ```
163
+
164
+ ### Step 7: Open in browser
165
+
166
+ ```
167
+ http://localhost:7860
168
+ ```
169
+
170
+ ---
171
+
172
+ ## โ˜๏ธ Deployment
173
+
174
+ ### AWS App Runner + ECR (Recommended)
175
+
176
+ ```bash
177
+ # 1. Authenticate Docker to AWS ECR
178
+ aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <account-id>.dkr.ecr.us-east-1.amazonaws.com
179
+
180
+ # 2. Build Docker image
181
+ docker build --no-cache -t medical-bot .
182
+
183
+ # 3. Tag image
184
+ docker tag medical-bot:latest <account-id>.dkr.ecr.us-east-1.amazonaws.com/medical-bot:latest
185
+
186
+ # 4. Push to ECR
187
+ docker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/medical-bot:latest
188
+ ```
189
+
190
+ Then deploy via AWS App Runner, set **port 7860**, and add your `PINECONE_API_KEY` and `GOOGLE_API_KEY` as environment variables.
191
+
192
+ ---
193
+
194
+ ## ๐Ÿ“ Project Structure
195
+
196
+ ```
197
+ โ”œโ”€โ”€ app.py # Flask web server
198
+ โ”œโ”€โ”€ store_index.py # Downloads dataset & indexes into Pinecone
199
+ โ”œโ”€โ”€ Dockerfile # Docker config for deployment
200
+ โ”œโ”€โ”€ requirements.txt # Python dependencies
201
+ โ”œโ”€โ”€ setup.py
202
+ โ”œโ”€โ”€ src/
203
+ โ”‚ โ”œโ”€โ”€ helper.py # load_hf_dataset(), text_split(), embeddings
204
+ โ”‚ โ””โ”€โ”€ prompt.py # System prompt for student support
205
+ โ”œโ”€โ”€ static/
206
+ โ”‚ โ””โ”€โ”€ style.css # Premium student-themed UI
207
+ โ””โ”€โ”€ templates/
208
+ โ””โ”€โ”€ chat.html # Chat interface
209
+ ```
210
+
211
+ ---
212
+
213
+ ## ๐Ÿ”‘ Required API Keys
214
+
215
+ | Key | Where to Get |
216
+ | ------------------ | ------------------------------------------------------------- |
217
+ | `PINECONE_API_KEY` | [app.pinecone.io](https://app.pinecone.io/) |
218
+ | `GOOGLE_API_KEY` | [aistudio.google.com](https://aistudio.google.com/app/apikey) |
app.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, jsonify, request
2
+ from src.helper import download_hugging_face_embeddings
3
+ from langchain_pinecone import PineconeVectorStore
4
+ from langchain_google_genai import ChatGoogleGenerativeAI
5
+ from langchain.chains import create_retrieval_chain
6
+ from langchain.chains.combine_documents import create_stuff_documents_chain
7
+ from langchain_core.prompts import ChatPromptTemplate
8
+ from dotenv import load_dotenv
9
+ from src.prompt import *
10
+ import os
11
+ import traceback
12
+
13
+
14
+ app = Flask(__name__)
15
+
16
+
17
+ load_dotenv(override=True)
18
+
19
+ PINECONE_API_KEY = os.environ.get("PINECONE_API_KEY")
20
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
21
+
22
+ os.environ["PINECONE_API_KEY"] = PINECONE_API_KEY
23
+ if GOOGLE_API_KEY:
24
+ os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEY
25
+
26
+
27
+ embeddings = download_hugging_face_embeddings()
28
+
29
+ index_name = os.environ.get("PINECONE_INDEX_NAME", "student-chatbot")
30
+ # Embed each chunk and upsert the embeddings into your Pinecone index.
31
+ docsearch = PineconeVectorStore.from_existing_index(
32
+ index_name=index_name, embedding=embeddings
33
+ )
34
+
35
+
36
+ retriever = docsearch.as_retriever(search_type="similarity", search_kwargs={"k": 3})
37
+
38
+ chatModel = ChatGoogleGenerativeAI(
39
+ model="gemini-2.5-flash",
40
+ temperature=0,
41
+ max_retries=2,
42
+ )
43
+ prompt = ChatPromptTemplate.from_messages(
44
+ [
45
+ ("system", system_prompt),
46
+ ("human", "{input}"),
47
+ ]
48
+ )
49
+
50
+ question_answer_chain = create_stuff_documents_chain(chatModel, prompt)
51
+ rag_chain = create_retrieval_chain(retriever, question_answer_chain)
52
+
53
+
54
+ def build_context_fallback_answer(user_query: str) -> str:
55
+ """Return a best-effort answer using retrieved context only (no LLM call)."""
56
+ try:
57
+ docs = retriever.invoke(user_query)
58
+ except Exception:
59
+ return "Gemini quota is reached, and I could not fetch context right now. Please try again shortly."
60
+
61
+ if not docs:
62
+ return "Gemini quota is reached, and I could not find relevant context for this question right now."
63
+
64
+ top_doc_text = (docs[0].page_content or "").strip()
65
+ if not top_doc_text:
66
+ return "Gemini quota is reached, but retrieved context is empty. Please try again later."
67
+
68
+ answer_line = None
69
+ for line in top_doc_text.splitlines():
70
+ if line.lower().startswith("answer:"):
71
+ answer_line = line.split(":", 1)[1].strip()
72
+ break
73
+
74
+ if answer_line:
75
+ return f"Gemini quota reached, so I am answering from stored context: {answer_line}"
76
+
77
+ snippet = " ".join(
78
+ part.strip() for part in top_doc_text.splitlines() if part.strip()
79
+ )
80
+ snippet = snippet[:450]
81
+ return "Gemini quota reached, so I am answering from stored context: " f"{snippet}"
82
+
83
+
84
+ @app.route("/")
85
+ def index():
86
+ return render_template("chat.html")
87
+
88
+
89
+ @app.route("/get", methods=["GET", "POST"])
90
+ def chat():
91
+ msg = request.values.get("msg", "").strip()
92
+
93
+ if not msg:
94
+ return "Please enter a question.", 200
95
+
96
+ print(msg)
97
+
98
+ if not GOOGLE_API_KEY:
99
+ return (
100
+ "GOOGLE_API_KEY is missing. Add it to your .env file and restart the app.",
101
+ 200,
102
+ )
103
+
104
+ try:
105
+ response = rag_chain.invoke({"input": msg})
106
+ answer = response.get("answer") if isinstance(response, dict) else None
107
+ if not answer:
108
+ return (
109
+ "I could not generate a response right now. Please try rephrasing your question.",
110
+ 200,
111
+ )
112
+
113
+ print("Response : ", answer)
114
+ return str(answer), 200
115
+ except Exception as e:
116
+ print("Error: ", str(e))
117
+ traceback.print_exc()
118
+
119
+ error_text = str(e).lower()
120
+ if (
121
+ "api key" in error_text
122
+ or "permission" in error_text
123
+ or "unauthorized" in error_text
124
+ ):
125
+ return (
126
+ "Your Gemini API key is invalid or missing permissions. Please verify GOOGLE_API_KEY.",
127
+ 200,
128
+ )
129
+ if "quota" in error_text or "rate" in error_text or "429" in error_text:
130
+ fallback_answer = build_context_fallback_answer(msg)
131
+ return fallback_answer, 200
132
+
133
+ return (
134
+ "I am having trouble reaching the AI service right now. Please try again in a few seconds.",
135
+ 200,
136
+ )
137
+
138
+
139
+ if __name__ == "__main__":
140
+ port = int(os.environ.get("PORT", 7860))
141
+ app.run(host="0.0.0.0", port=port, debug=False)
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ datasets
2
+ langchain>=0.3.0,<0.4.0
3
+ flask>=3.0.0
4
+ sentence-transformers>=3.0.0
5
+ pypdf>=4.0.0
6
+ python-dotenv>=1.0.0
7
+ langchain-pinecone>=0.2.0
8
+ langchain-openai>=0.2.0
9
+ langchain-google-genai>=2.0.0
10
+ langchain-community>=0.3.0,<0.4.0
11
+ gunicorn>=22.0.0
12
+ -e .
research/trials.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
setup.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from setuptools import find_packages, setup
2
+
3
+ setup(
4
+ name="medical_chatbot",
5
+ version="0.1.0",
6
+ author="Boktiar Ahmed Bappy",
7
+ author_email="entbappy73@gmail.com",
8
+ packages=find_packages(),
9
+ install_requires=[]
10
+ )
src/__init__.py ADDED
File without changes
src/helper.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
2
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
3
+ from langchain_community.embeddings import HuggingFaceEmbeddings
4
+ from langchain_core.documents import Document
5
+ from datasets import load_dataset
6
+ from typing import List, Optional, Dict, Any
7
+ import csv
8
+ import json
9
+ from pathlib import Path
10
+
11
+
12
+ # Extract Data From PDF file(s)
13
+ def load_pdf_file(data_path: str) -> List[Document]:
14
+ path = Path(data_path)
15
+ if not path.exists():
16
+ raise FileNotFoundError(f"PDF path not found: {data_path}")
17
+
18
+ if path.is_file():
19
+ if path.suffix.lower() != ".pdf":
20
+ raise ValueError(
21
+ "When DATA_SOURCE=pdf and path is a file, it must be a .pdf file"
22
+ )
23
+ return PyPDFLoader(str(path)).load()
24
+
25
+ loader = DirectoryLoader(str(path), glob="*.pdf", loader_cls=PyPDFLoader)
26
+ return loader.load()
27
+
28
+
29
+ # Load Student Q&A Dataset from Hugging Face
30
+ def load_hf_dataset(dataset_name: str) -> List[Document]:
31
+ dataset = load_dataset(dataset_name, split="train")
32
+ documents = []
33
+ for row in dataset:
34
+ # Build a rich text blob from all available fields in the row
35
+ content_parts = []
36
+ for key, value in row.items():
37
+ if value:
38
+ content_parts.append(f"{key.capitalize()}: {value}")
39
+ page_content = "\n".join(content_parts)
40
+ documents.append(
41
+ Document(page_content=page_content, metadata={"source": dataset_name})
42
+ )
43
+ return documents
44
+
45
+
46
+ def _parse_columns(columns: Optional[str]) -> Optional[List[str]]:
47
+ if not columns:
48
+ return None
49
+ parsed = [col.strip() for col in columns.split(",") if col.strip()]
50
+ return parsed or None
51
+
52
+
53
+ def _row_to_document(
54
+ row: Dict[str, Any],
55
+ source_name: str,
56
+ text_columns: Optional[List[str]] = None,
57
+ ) -> Optional[Document]:
58
+ content_parts: List[str] = []
59
+
60
+ if text_columns:
61
+ for key in text_columns:
62
+ value = row.get(key)
63
+ if value is not None and str(value).strip():
64
+ content_parts.append(f"{key.capitalize()}: {value}")
65
+ else:
66
+ for key, value in row.items():
67
+ if value is not None and str(value).strip():
68
+ content_parts.append(f"{key.capitalize()}: {value}")
69
+
70
+ if not content_parts:
71
+ return None
72
+
73
+ return Document(
74
+ page_content="\n".join(content_parts),
75
+ metadata={"source": source_name},
76
+ )
77
+
78
+
79
+ def load_local_dataset(
80
+ local_path: str, text_columns: Optional[str] = None
81
+ ) -> List[Document]:
82
+ path = Path(local_path)
83
+ if not path.exists():
84
+ raise FileNotFoundError(f"Dataset file not found: {local_path}")
85
+
86
+ parsed_text_columns = _parse_columns(text_columns)
87
+ ext = path.suffix.lower()
88
+ documents: List[Document] = []
89
+
90
+ if ext == ".csv":
91
+ with path.open("r", encoding="utf-8") as f:
92
+ reader = csv.DictReader(f)
93
+ for row in reader:
94
+ doc = _row_to_document(
95
+ row, source_name=str(path.name), text_columns=parsed_text_columns
96
+ )
97
+ if doc:
98
+ documents.append(doc)
99
+ return documents
100
+
101
+ if ext == ".json":
102
+ with path.open("r", encoding="utf-8") as f:
103
+ payload = json.load(f)
104
+
105
+ if isinstance(payload, dict):
106
+ payload = [payload]
107
+ if not isinstance(payload, list):
108
+ raise ValueError("JSON dataset must be an object or a list of objects")
109
+
110
+ for row in payload:
111
+ if isinstance(row, dict):
112
+ doc = _row_to_document(
113
+ row, source_name=str(path.name), text_columns=parsed_text_columns
114
+ )
115
+ if doc:
116
+ documents.append(doc)
117
+ return documents
118
+
119
+ if ext == ".jsonl":
120
+ with path.open("r", encoding="utf-8") as f:
121
+ for line in f:
122
+ line = line.strip()
123
+ if not line:
124
+ continue
125
+ row = json.loads(line)
126
+ if isinstance(row, dict):
127
+ doc = _row_to_document(
128
+ row,
129
+ source_name=str(path.name),
130
+ text_columns=parsed_text_columns,
131
+ )
132
+ if doc:
133
+ documents.append(doc)
134
+ return documents
135
+
136
+ if ext in {".txt", ".md"}:
137
+ text = path.read_text(encoding="utf-8")
138
+ blocks = [block.strip() for block in text.split("\n\n") if block.strip()]
139
+ for block in blocks:
140
+ documents.append(
141
+ Document(page_content=block, metadata={"source": str(path.name)})
142
+ )
143
+ return documents
144
+
145
+ raise ValueError(
146
+ "Unsupported dataset format. Use .csv, .json, .jsonl, .txt, or .md"
147
+ )
148
+
149
+
150
+ def load_dataset_by_config(
151
+ data_source: str,
152
+ hf_dataset_name: Optional[str] = None,
153
+ local_dataset_path: Optional[str] = None,
154
+ text_columns: Optional[str] = None,
155
+ ) -> List[Document]:
156
+ source = (data_source or "hf").strip().lower()
157
+
158
+ if source == "hf":
159
+ if not hf_dataset_name:
160
+ raise ValueError("HF_DATASET_NAME is required when DATA_SOURCE=hf")
161
+ return load_hf_dataset(hf_dataset_name)
162
+
163
+ if source == "local":
164
+ if not local_dataset_path:
165
+ raise ValueError("LOCAL_DATASET_PATH is required when DATA_SOURCE=local")
166
+ return load_local_dataset(local_dataset_path, text_columns=text_columns)
167
+
168
+ if source == "pdf":
169
+ if not local_dataset_path:
170
+ raise ValueError("LOCAL_DATASET_PATH is required when DATA_SOURCE=pdf")
171
+ return load_pdf_file(local_dataset_path)
172
+
173
+ raise ValueError("DATA_SOURCE must be one of: hf, local, pdf")
174
+
175
+
176
+ def filter_to_minimal_docs(docs: List[Document]) -> List[Document]:
177
+ """
178
+ Given a list of Document objects, return a new list of Document objects
179
+ containing only 'source' in metadata and the original page_content.
180
+ """
181
+ minimal_docs: List[Document] = []
182
+ for doc in docs:
183
+ src = doc.metadata.get("source")
184
+ minimal_docs.append(
185
+ Document(page_content=doc.page_content, metadata={"source": src})
186
+ )
187
+ return minimal_docs
188
+
189
+
190
+ # Split the Data into Text Chunks
191
+ def text_split(extracted_data):
192
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=20)
193
+ text_chunks = text_splitter.split_documents(extracted_data)
194
+ return text_chunks
195
+
196
+
197
+ # Download the Embeddings from HuggingFace
198
+ def download_hugging_face_embeddings():
199
+ embeddings = HuggingFaceEmbeddings(
200
+ model_name="sentence-transformers/all-MiniLM-L6-v2"
201
+ ) # this model return 384 dimensions
202
+ return embeddings
src/prompt.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ system_prompt = (
2
+ "You are a friendly and helpful student support assistant. "
3
+ "Use the following pieces of retrieved context to answer the student's question accurately. "
4
+ "If you don't know the answer, say that you don't know. "
5
+ "Keep your answer concise and easy to understand for a student."
6
+ "\n\n"
7
+ "{context}"
8
+ )
static/style.css ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ :root {
10
+ --primary: #7c3aed;
11
+ --primary-light: #a78bfa;
12
+ --primary-dark: #5b21b6;
13
+ --secondary: #f59e0b;
14
+ --bg-deep: #0f0a1e;
15
+ --bg-card: rgba(26, 17, 52, 0.85);
16
+ --bg-message: rgba(49, 32, 95, 0.7);
17
+ --border: rgba(167, 139, 250, 0.15);
18
+ --text-primary: #f5f3ff;
19
+ --text-muted: #a78bfa;
20
+ --user-gradient: linear-gradient(135deg, #7c3aed, #a855f7);
21
+ --accent-glow: rgba(124, 58, 237, 0.4);
22
+ }
23
+
24
+ body, html {
25
+ height: 100%;
26
+ font-family: 'Plus Jakarta Sans', 'Inter', sans-serif;
27
+ background: var(--bg-deep);
28
+ color: var(--text-primary);
29
+ overflow: hidden;
30
+ }
31
+
32
+ /* Animated background */
33
+ body::before {
34
+ content: '';
35
+ position: fixed;
36
+ top: -50%;
37
+ left: -50%;
38
+ width: 200%;
39
+ height: 200%;
40
+ background:
41
+ radial-gradient(ellipse at 20% 20%, rgba(124, 58, 237, 0.15) 0%, transparent 50%),
42
+ radial-gradient(ellipse at 80% 80%, rgba(168, 85, 247, 0.1) 0%, transparent 50%),
43
+ radial-gradient(ellipse at 50% 50%, rgba(245, 158, 11, 0.05) 0%, transparent 60%);
44
+ z-index: -1;
45
+ animation: bgPulse 8s ease-in-out infinite alternate;
46
+ }
47
+
48
+ @keyframes bgPulse {
49
+ 0% { transform: scale(1) rotate(0deg); }
50
+ 100% { transform: scale(1.05) rotate(2deg); }
51
+ }
52
+
53
+ /* Floating particles */
54
+ body::after {
55
+ content: '๐ŸŽ“ ๐Ÿ“š โœ๏ธ ๐Ÿซ ๐Ÿ“';
56
+ position: fixed;
57
+ top: 5%;
58
+ right: 3%;
59
+ font-size: 1.2rem;
60
+ opacity: 0.08;
61
+ letter-spacing: 8px;
62
+ writing-mode: vertical-rl;
63
+ z-index: -1;
64
+ }
65
+
66
+ .chat-container {
67
+ display: flex;
68
+ flex-direction: column;
69
+ max-width: 820px;
70
+ height: 92vh;
71
+ margin: 4vh auto;
72
+ background: var(--bg-card);
73
+ border-radius: 24px;
74
+ box-shadow:
75
+ 0 32px 64px -12px rgba(0, 0, 0, 0.6),
76
+ 0 0 0 1px var(--border),
77
+ inset 0 1px 0 rgba(167, 139, 250, 0.1);
78
+ backdrop-filter: blur(20px);
79
+ overflow: hidden;
80
+ }
81
+
82
+ /* โ”€โ”€โ”€ HEADER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
83
+ .chat-header {
84
+ padding: 18px 28px;
85
+ background: linear-gradient(135deg, rgba(91, 33, 182, 0.6), rgba(124, 58, 237, 0.4));
86
+ border-bottom: 1px solid var(--border);
87
+ display: flex;
88
+ align-items: center;
89
+ justify-content: space-between;
90
+ position: relative;
91
+ overflow: hidden;
92
+ }
93
+
94
+ .chat-header::before {
95
+ content: '';
96
+ position: absolute;
97
+ top: 0; left: 0; right: 0; bottom: 0;
98
+ background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23a78bfa' fill-opacity='0.04'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
99
+ opacity: 0.5;
100
+ }
101
+
102
+ .bot-info {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 14px;
106
+ position: relative;
107
+ }
108
+
109
+ .avatar-container {
110
+ position: relative;
111
+ }
112
+
113
+ .bot-avatar {
114
+ width: 52px;
115
+ height: 52px;
116
+ border-radius: 16px;
117
+ object-fit: cover;
118
+ background: linear-gradient(135deg, #7c3aed, #a855f7);
119
+ padding: 6px;
120
+ box-shadow: 0 0 20px var(--accent-glow), 0 0 0 2px rgba(167, 139, 250, 0.3);
121
+ }
122
+
123
+ .avatar {
124
+ width: 38px;
125
+ height: 38px;
126
+ border-radius: 12px;
127
+ object-fit: cover;
128
+ background: linear-gradient(135deg, #7c3aed, #a855f7);
129
+ padding: 4px;
130
+ flex-shrink: 0;
131
+ }
132
+
133
+ .user-message .avatar {
134
+ background: linear-gradient(135deg, #f59e0b, #fbbf24);
135
+ padding: 3px;
136
+ }
137
+
138
+ .status-indicator {
139
+ position: absolute;
140
+ bottom: -2px;
141
+ right: -2px;
142
+ width: 14px;
143
+ height: 14px;
144
+ background: #10b981;
145
+ border-radius: 50%;
146
+ border: 2px solid #0f0a1e;
147
+ box-shadow: 0 0 8px rgba(16, 185, 129, 0.7);
148
+ animation: pulse-status 2s ease-in-out infinite;
149
+ }
150
+
151
+ @keyframes pulse-status {
152
+ 0%, 100% { box-shadow: 0 0 8px rgba(16, 185, 129, 0.7); }
153
+ 50% { box-shadow: 0 0 16px rgba(16, 185, 129, 1); }
154
+ }
155
+
156
+ .text-info h1 {
157
+ font-size: 1.2rem;
158
+ font-weight: 700;
159
+ color: #f5f3ff;
160
+ letter-spacing: 0.3px;
161
+ margin-bottom: 3px;
162
+ }
163
+
164
+ .text-info p {
165
+ font-size: 0.8rem;
166
+ color: var(--text-muted);
167
+ font-weight: 500;
168
+ }
169
+
170
+ /* Header badge */
171
+ .header-badge {
172
+ background: rgba(245, 158, 11, 0.15);
173
+ border: 1px solid rgba(245, 158, 11, 0.3);
174
+ color: #fbbf24;
175
+ font-size: 0.72rem;
176
+ font-weight: 600;
177
+ padding: 5px 12px;
178
+ border-radius: 20px;
179
+ letter-spacing: 0.5px;
180
+ text-transform: uppercase;
181
+ }
182
+
183
+ /* โ”€โ”€โ”€ CHAT BOX โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
184
+ .chat-box {
185
+ flex: 1;
186
+ overflow-y: auto;
187
+ padding: 28px;
188
+ display: flex;
189
+ flex-direction: column;
190
+ gap: 18px;
191
+ scroll-behavior: smooth;
192
+ }
193
+
194
+ .chat-box::-webkit-scrollbar { width: 6px; }
195
+ .chat-box::-webkit-scrollbar-track { background: transparent; }
196
+ .chat-box::-webkit-scrollbar-thumb {
197
+ background: rgba(167, 139, 250, 0.2);
198
+ border-radius: 4px;
199
+ }
200
+ .chat-box::-webkit-scrollbar-thumb:hover {
201
+ background: rgba(167, 139, 250, 0.4);
202
+ }
203
+
204
+ /* โ”€โ”€โ”€ MESSAGES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
205
+ .message {
206
+ display: flex;
207
+ gap: 12px;
208
+ max-width: 82%;
209
+ align-items: flex-end;
210
+ }
211
+
212
+ .user-message { align-self: flex-end; }
213
+ .bot-message { align-self: flex-start; }
214
+
215
+ .message-content {
216
+ padding: 14px 18px;
217
+ border-radius: 18px;
218
+ font-size: 0.93rem;
219
+ line-height: 1.6;
220
+ position: relative;
221
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
222
+ }
223
+
224
+ .bot-message .message-content {
225
+ background: var(--bg-message);
226
+ color: #ede9fe;
227
+ border-bottom-left-radius: 4px;
228
+ border: 1px solid var(--border);
229
+ }
230
+
231
+ .user-message .message-content {
232
+ background: var(--user-gradient);
233
+ color: #ffffff;
234
+ border-bottom-right-radius: 4px;
235
+ box-shadow: 0 4px 20px var(--accent-glow);
236
+ }
237
+
238
+ .error-message .message-content {
239
+ background: rgba(239, 68, 68, 0.15);
240
+ border: 1px solid rgba(239, 68, 68, 0.35);
241
+ color: #fca5a5;
242
+ }
243
+
244
+ .time {
245
+ display: block;
246
+ font-size: 0.68rem;
247
+ margin-top: 7px;
248
+ opacity: 0.6;
249
+ text-align: right;
250
+ font-weight: 500;
251
+ }
252
+
253
+ /* โ”€โ”€โ”€ FOOTER / INPUT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
254
+ .chat-footer {
255
+ padding: 18px 28px 22px;
256
+ background: rgba(91, 33, 182, 0.12);
257
+ border-top: 1px solid var(--border);
258
+ }
259
+
260
+ .input-form {
261
+ display: flex;
262
+ gap: 12px;
263
+ background: rgba(167, 139, 250, 0.07);
264
+ border: 1.5px solid rgba(167, 139, 250, 0.2);
265
+ border-radius: 32px;
266
+ padding: 8px 8px 8px 22px;
267
+ transition: all 0.3s ease;
268
+ }
269
+
270
+ .input-form:focus-within {
271
+ border-color: rgba(167, 139, 250, 0.6);
272
+ background: rgba(167, 139, 250, 0.1);
273
+ box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.12);
274
+ }
275
+
276
+ .input-form input {
277
+ flex: 1;
278
+ background: transparent;
279
+ border: none;
280
+ color: #f5f3ff;
281
+ font-size: 0.95rem;
282
+ outline: none;
283
+ font-family: inherit;
284
+ font-weight: 500;
285
+ }
286
+
287
+ .input-form input::placeholder { color: #6d5cae; }
288
+
289
+ .input-form button {
290
+ background: var(--user-gradient);
291
+ border: none;
292
+ border-radius: 50%;
293
+ width: 46px;
294
+ height: 46px;
295
+ color: white;
296
+ font-size: 1rem;
297
+ cursor: pointer;
298
+ display: flex;
299
+ align-items: center;
300
+ justify-content: center;
301
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
302
+ flex-shrink: 0;
303
+ box-shadow: 0 4px 12px var(--accent-glow);
304
+ }
305
+
306
+ .input-form button:hover {
307
+ transform: scale(1.08);
308
+ box-shadow: 0 6px 20px var(--accent-glow);
309
+ }
310
+
311
+ .input-form button:active { transform: scale(0.94); }
312
+
313
+ /* โ”€โ”€โ”€ ANIMATIONS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
314
+ @keyframes slideIn {
315
+ from { opacity: 0; transform: translateY(16px); }
316
+ to { opacity: 1; transform: translateY(0); }
317
+ }
318
+
319
+ .slide-in { animation: slideIn 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
320
+
321
+ .intro-animation {
322
+ animation: slideIn 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
323
+ animation-delay: 0.2s;
324
+ }
325
+
326
+ /* โ”€โ”€โ”€ TYPING INDICATOR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
327
+ .typing-dots {
328
+ display: flex;
329
+ gap: 5px;
330
+ padding: 4px 6px;
331
+ }
332
+
333
+ .typing-dots span {
334
+ width: 8px;
335
+ height: 8px;
336
+ background: var(--primary-light);
337
+ border-radius: 50%;
338
+ animation: blink 1.4s infinite both;
339
+ }
340
+
341
+ .typing-dots span:nth-child(1) { animation-delay: 0.2s; }
342
+ .typing-dots span:nth-child(2) { animation-delay: 0.4s; }
343
+ .typing-dots span:nth-child(3) { animation-delay: 0.6s; }
344
+
345
+ @keyframes blink {
346
+ 0% { opacity: 0.2; transform: scale(0.8) translateY(0); }
347
+ 40% { opacity: 1; transform: scale(1.1) translateY(-3px); }
348
+ 100% { opacity: 0.2; transform: scale(0.8) translateY(0); }
349
+ }
350
+
351
+ /* โ”€โ”€โ”€ RESPONSIVE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
352
+ @media (max-width: 768px) {
353
+ .chat-container {
354
+ margin: 0;
355
+ height: 100vh;
356
+ border-radius: 0;
357
+ max-width: 100%;
358
+ }
359
+ .header-badge { display: none; }
360
+ }
store_index.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import os
3
+ from src.helper import (
4
+ load_dataset_by_config,
5
+ filter_to_minimal_docs,
6
+ text_split,
7
+ download_hugging_face_embeddings,
8
+ )
9
+ from pinecone import Pinecone
10
+ from pinecone import ServerlessSpec
11
+ from langchain_pinecone import PineconeVectorStore
12
+
13
+ load_dotenv(override=True)
14
+
15
+
16
+ PINECONE_API_KEY = os.environ.get("PINECONE_API_KEY")
17
+ os.environ["PINECONE_API_KEY"] = PINECONE_API_KEY
18
+
19
+
20
+ DATA_SOURCE = os.environ.get("DATA_SOURCE", "hf")
21
+ HF_DATASET_NAME = os.environ.get(
22
+ "HF_DATASET_NAME", "bot-remains/student-assistance-chatbot"
23
+ )
24
+ LOCAL_DATASET_PATH = os.environ.get("LOCAL_DATASET_PATH", "")
25
+ TEXT_COLUMNS = os.environ.get("TEXT_COLUMNS", "")
26
+ PINECONE_INDEX_NAME = os.environ.get("PINECONE_INDEX_NAME", "student-chatbot")
27
+ PINECONE_CLOUD = os.environ.get("PINECONE_CLOUD", "aws")
28
+ PINECONE_REGION = os.environ.get("PINECONE_REGION", "us-east-1")
29
+ RESET_INDEX = os.environ.get("RESET_INDEX", "false").strip().lower() == "true"
30
+
31
+
32
+ extracted_data = load_dataset_by_config(
33
+ data_source=DATA_SOURCE,
34
+ hf_dataset_name=HF_DATASET_NAME,
35
+ local_dataset_path=LOCAL_DATASET_PATH,
36
+ text_columns=TEXT_COLUMNS,
37
+ )
38
+ if not extracted_data:
39
+ raise ValueError("No documents were loaded from the selected dataset source")
40
+
41
+ filter_data = filter_to_minimal_docs(extracted_data)
42
+ text_chunks = text_split(filter_data)
43
+
44
+ embeddings = download_hugging_face_embeddings()
45
+
46
+ pinecone_api_key = PINECONE_API_KEY
47
+ pc = Pinecone(api_key=pinecone_api_key)
48
+
49
+
50
+ index_name = PINECONE_INDEX_NAME
51
+
52
+ if RESET_INDEX and pc.has_index(index_name):
53
+ pc.delete_index(index_name)
54
+ print(f"Deleted existing Pinecone index '{index_name}' because RESET_INDEX=true")
55
+
56
+ if not pc.has_index(index_name):
57
+ pc.create_index(
58
+ name=index_name,
59
+ dimension=384,
60
+ metric="cosine",
61
+ spec=ServerlessSpec(cloud=PINECONE_CLOUD, region=PINECONE_REGION),
62
+ )
63
+
64
+ index = pc.Index(index_name)
65
+
66
+
67
+ docsearch = PineconeVectorStore.from_documents(
68
+ documents=text_chunks,
69
+ index_name=index_name,
70
+ embedding=embeddings,
71
+ )
72
+
73
+ print(
74
+ f"Indexed {len(text_chunks)} chunks into Pinecone index '{index_name}' from source '{DATA_SOURCE}'."
75
+ )
template.sh ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Creating directory
2
+ mkdir -p src
3
+ mkdir -p research
4
+
5
+ # Creating files
6
+ touch src/__init__.py
7
+ touch src/helper.py
8
+ touch src/prompt.py
9
+ touch .env
10
+ touch setup.py
11
+ touch app.py
12
+ touch research/trials.ipynb
13
+ touch requirements.txt
14
+
15
+
16
+ echo "Directory and files created successfully!."
templates/chat.html ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <title>Student Support Chatbot</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
10
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css')}}" />
11
+ </head>
12
+
13
+ <body>
14
+ <div class="chat-container">
15
+ <header class="chat-header">
16
+ <div class="bot-info">
17
+ <div class="avatar-container">
18
+ <img src="https://cdn-icons-png.flaticon.com/512/3135/3135715.png" class="bot-avatar"
19
+ alt="Student Bot">
20
+ <span class="status-indicator"></span>
21
+ </div>
22
+ <div class="text-info">
23
+ <h1>Student Support Assistant</h1>
24
+ <p>Ask me anything about college & academics</p>
25
+ </div>
26
+ </div>
27
+ <span class="header-badge">๐ŸŽ“ AI Powered</span>
28
+ </header>
29
+
30
+ <main id="chat-box" class="chat-box">
31
+ <div class="message bot-message intro-animation">
32
+ <img src="https://cdn-icons-png.flaticon.com/512/3135/3135715.png" class="avatar" alt="Bot">
33
+ <div class="message-content">
34
+ <p>Hello! I am your Student Support Assistant ๐ŸŽ“ Ask me anything about admissions, courses, fees, or campus life!</p>
35
+ </div>
36
+ </div>
37
+ </main>
38
+
39
+ <footer class="chat-footer">
40
+ <form id="messageArea" class="input-form">
41
+ <input type="text" id="text" name="msg" placeholder="Type your message here..." autocomplete="off"
42
+ required>
43
+ <button type="submit" id="send"><i class="fas fa-paper-plane"></i></button>
44
+ </form>
45
+ </footer>
46
+ </div>
47
+
48
+ <script>
49
+ $(document).ready(function () {
50
+ const chatBox = $('#chat-box');
51
+
52
+ function scrollToBottom() {
53
+ chatBox.animate({ scrollTop: chatBox.prop("scrollHeight") }, 300);
54
+ }
55
+
56
+ function formatTime() {
57
+ const date = new Date();
58
+ let hours = date.getHours();
59
+ let minutes = date.getMinutes();
60
+ const ampm = hours >= 12 ? 'PM' : 'AM';
61
+ hours = hours % 12;
62
+ hours = hours ? hours : 12;
63
+ minutes = minutes < 10 ? '0' + minutes : minutes;
64
+ return hours + ':' + minutes + ' ' + ampm;
65
+ }
66
+
67
+ function showTypingIndicator() {
68
+ const html = `
69
+ <div class="message bot-message typing-indicator slide-in" id="typing">
70
+ <img src="https://cdn-icons-png.flaticon.com/512/3135/3135715.png" class="avatar" alt="Bot">
71
+ <div class="message-content">
72
+ <div class="typing-dots">
73
+ <span></span><span></span><span></span>
74
+ </div>
75
+ </div>
76
+ </div>`;
77
+ chatBox.append(html);
78
+ scrollToBottom();
79
+ }
80
+
81
+ function removeTypingIndicator() {
82
+ $('#typing').remove();
83
+ }
84
+
85
+ $("#messageArea").on("submit", function (event) {
86
+ event.preventDefault();
87
+
88
+ const rawText = $("#text").val().trim();
89
+ if (!rawText) return;
90
+
91
+ const time = formatTime();
92
+ const userHtml = `
93
+ <div class="message user-message slide-in">
94
+ <div class="message-content">
95
+ <p>${rawText}</p>
96
+ <span class="time">${time}</span>
97
+ </div>
98
+ <img src="https://i.ibb.co/d5b84Xw/Untitled-design.png" class="avatar" alt="User">
99
+ </div>`;
100
+
101
+ $("#text").val("");
102
+ chatBox.append(userHtml);
103
+ scrollToBottom();
104
+ showTypingIndicator();
105
+
106
+ $.ajax({
107
+ data: { msg: rawText },
108
+ type: "POST",
109
+ url: "/get",
110
+ }).done(function (data) {
111
+ removeTypingIndicator();
112
+ const botHtml = `
113
+ <div class="message bot-message slide-in">
114
+ <img src="https://cdn-icons-png.flaticon.com/512/387/387569.png" class="avatar" alt="Bot">
115
+ <div class="message-content">
116
+ <p>${data}</p>
117
+ <span class="time">${formatTime()}</span>
118
+ </div>
119
+ </div>`;
120
+ chatBox.append(botHtml);
121
+ scrollToBottom();
122
+ }).fail(function (jqXHR) {
123
+ removeTypingIndicator();
124
+ let errorMessage = "Sorry, I am currently facing an internal error. Please check your API keys or try again later.";
125
+ if (jqXHR.responseText) {
126
+ errorMessage = jqXHR.responseText;
127
+ }
128
+ const errorHtml = `
129
+ <div class="message bot-message error-message slide-in">
130
+ <img src="https://cdn-icons-png.flaticon.com/512/387/387569.png" class="avatar" alt="Bot">
131
+ <div class="message-content">
132
+ <p><i class="fas fa-exclamation-triangle"></i> ${errorMessage}</p>
133
+ </div>
134
+ </div>`;
135
+ chatBox.append(errorHtml);
136
+ scrollToBottom();
137
+ });
138
+ });
139
+ });
140
+ </script>
141
+ </body>
142
+
143
+ </html>