Spaces:
Sleeping
Sleeping
Commit
·
0ec978f
1
Parent(s):
fedd57c
Initial commit with Database and PDF
Browse files- .env.example +1 -0
- .github/workflows/sync_to_hub.yml +20 -0
- .gitignore +207 -0
- Dockerfile +13 -0
- LICENSE +21 -0
- README.md +121 -0
- api/__init__.py +0 -0
- api/main.py +82 -0
- api/schemas.py +16 -0
- chroma_db/6a26d0e5-feba-4f8b-a92a-f8d1220144dc/data_level0.bin +3 -0
- chroma_db/6a26d0e5-feba-4f8b-a92a-f8d1220144dc/header.bin +3 -0
- chroma_db/6a26d0e5-feba-4f8b-a92a-f8d1220144dc/length.bin +3 -0
- chroma_db/6a26d0e5-feba-4f8b-a92a-f8d1220144dc/link_lists.bin +0 -0
- chroma_db/chroma.sqlite3 +3 -0
- config/__init.py +0 -0
- config/settings.py +21 -0
- core/__init.py +0 -0
- core/prompts.py +37 -0
- core/rag_pipeline.py +72 -0
- data/employee_handbook_-_2024.pdf +3 -0
- ingest.py +56 -0
- requirements.txt +14 -0
- services/__init.py +0 -0
- services/document_processor.py +51 -0
- services/llm_client.py +26 -0
- services/vector_store.py +49 -0
- utils/__init.py +0 -0
- utils/vision_helper.py +0 -0
- web_ui/index.html +54 -0
- web_ui/script.js +189 -0
- web_ui/style.css +139 -0
.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
GROQ_API_KEY=your_groq_api_key_here
|
.github/workflows/sync_to_hub.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Sync to Hugging Face hub
|
| 2 |
+
on:
|
| 3 |
+
push:
|
| 4 |
+
branches: [main]
|
| 5 |
+
|
| 6 |
+
workflow_dispatch:
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
sync-to-hub:
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
steps:
|
| 12 |
+
- uses: actions/checkout@v3
|
| 13 |
+
with:
|
| 14 |
+
fetch-depth: 0
|
| 15 |
+
lfs: true
|
| 16 |
+
- name: Push to hub
|
| 17 |
+
env:
|
| 18 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 19 |
+
run: |
|
| 20 |
+
git push https://YousefMohtady1:$HF_TOKEN@huggingface.co/spaces/YousefMohtady1/CorpGuideAI main
|
.gitignore
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[codz]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
share/python-wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# PyInstaller
|
| 30 |
+
# Usually these files are written by a python script from a template
|
| 31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 32 |
+
*.manifest
|
| 33 |
+
*.spec
|
| 34 |
+
|
| 35 |
+
# Installer logs
|
| 36 |
+
pip-log.txt
|
| 37 |
+
pip-delete-this-directory.txt
|
| 38 |
+
|
| 39 |
+
# Unit test / coverage reports
|
| 40 |
+
htmlcov/
|
| 41 |
+
.tox/
|
| 42 |
+
.nox/
|
| 43 |
+
.coverage
|
| 44 |
+
.coverage.*
|
| 45 |
+
.cache
|
| 46 |
+
nosetests.xml
|
| 47 |
+
coverage.xml
|
| 48 |
+
*.cover
|
| 49 |
+
*.py.cover
|
| 50 |
+
.hypothesis/
|
| 51 |
+
.pytest_cache/
|
| 52 |
+
cover/
|
| 53 |
+
|
| 54 |
+
# Translations
|
| 55 |
+
*.mo
|
| 56 |
+
*.pot
|
| 57 |
+
|
| 58 |
+
# Django stuff:
|
| 59 |
+
*.log
|
| 60 |
+
local_settings.py
|
| 61 |
+
db.sqlite3
|
| 62 |
+
db.sqlite3-journal
|
| 63 |
+
|
| 64 |
+
# Flask stuff:
|
| 65 |
+
instance/
|
| 66 |
+
.webassets-cache
|
| 67 |
+
|
| 68 |
+
# Scrapy stuff:
|
| 69 |
+
.scrapy
|
| 70 |
+
|
| 71 |
+
# Sphinx documentation
|
| 72 |
+
docs/_build/
|
| 73 |
+
|
| 74 |
+
# PyBuilder
|
| 75 |
+
.pybuilder/
|
| 76 |
+
target/
|
| 77 |
+
|
| 78 |
+
# Jupyter Notebook
|
| 79 |
+
.ipynb_checkpoints
|
| 80 |
+
|
| 81 |
+
# IPython
|
| 82 |
+
profile_default/
|
| 83 |
+
ipython_config.py
|
| 84 |
+
|
| 85 |
+
# pyenv
|
| 86 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 88 |
+
# .python-version
|
| 89 |
+
|
| 90 |
+
# pipenv
|
| 91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 94 |
+
# install all needed dependencies.
|
| 95 |
+
#Pipfile.lock
|
| 96 |
+
|
| 97 |
+
# UV
|
| 98 |
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
| 99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 100 |
+
# commonly ignored for libraries.
|
| 101 |
+
#uv.lock
|
| 102 |
+
|
| 103 |
+
# poetry
|
| 104 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 105 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 106 |
+
# commonly ignored for libraries.
|
| 107 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 108 |
+
#poetry.lock
|
| 109 |
+
#poetry.toml
|
| 110 |
+
|
| 111 |
+
# pdm
|
| 112 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 113 |
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
| 114 |
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
| 115 |
+
#pdm.lock
|
| 116 |
+
#pdm.toml
|
| 117 |
+
.pdm-python
|
| 118 |
+
.pdm-build/
|
| 119 |
+
|
| 120 |
+
# pixi
|
| 121 |
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
| 122 |
+
#pixi.lock
|
| 123 |
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
| 124 |
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
| 125 |
+
.pixi
|
| 126 |
+
|
| 127 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 128 |
+
__pypackages__/
|
| 129 |
+
|
| 130 |
+
# Celery stuff
|
| 131 |
+
celerybeat-schedule
|
| 132 |
+
celerybeat.pid
|
| 133 |
+
|
| 134 |
+
# SageMath parsed files
|
| 135 |
+
*.sage.py
|
| 136 |
+
|
| 137 |
+
# Environments
|
| 138 |
+
.env
|
| 139 |
+
.envrc
|
| 140 |
+
.venv
|
| 141 |
+
env/
|
| 142 |
+
venv/
|
| 143 |
+
ENV/
|
| 144 |
+
env.bak/
|
| 145 |
+
venv.bak/
|
| 146 |
+
|
| 147 |
+
# Spyder project settings
|
| 148 |
+
.spyderproject
|
| 149 |
+
.spyproject
|
| 150 |
+
|
| 151 |
+
# Rope project settings
|
| 152 |
+
.ropeproject
|
| 153 |
+
|
| 154 |
+
# mkdocs documentation
|
| 155 |
+
/site
|
| 156 |
+
|
| 157 |
+
# mypy
|
| 158 |
+
.mypy_cache/
|
| 159 |
+
.dmypy.json
|
| 160 |
+
dmypy.json
|
| 161 |
+
|
| 162 |
+
# Pyre type checker
|
| 163 |
+
.pyre/
|
| 164 |
+
|
| 165 |
+
# pytype static type analyzer
|
| 166 |
+
.pytype/
|
| 167 |
+
|
| 168 |
+
# Cython debug symbols
|
| 169 |
+
cython_debug/
|
| 170 |
+
|
| 171 |
+
# PyCharm
|
| 172 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 173 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 174 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 175 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 176 |
+
#.idea/
|
| 177 |
+
|
| 178 |
+
# Abstra
|
| 179 |
+
# Abstra is an AI-powered process automation framework.
|
| 180 |
+
# Ignore directories containing user credentials, local state, and settings.
|
| 181 |
+
# Learn more at https://abstra.io/docs
|
| 182 |
+
.abstra/
|
| 183 |
+
|
| 184 |
+
# Visual Studio Code
|
| 185 |
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
| 186 |
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
| 187 |
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
| 188 |
+
# you could uncomment the following to ignore the entire vscode folder
|
| 189 |
+
# .vscode/
|
| 190 |
+
|
| 191 |
+
# Ruff stuff:
|
| 192 |
+
.ruff_cache/
|
| 193 |
+
|
| 194 |
+
# PyPI configuration file
|
| 195 |
+
.pypirc
|
| 196 |
+
|
| 197 |
+
# Cursor
|
| 198 |
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
| 199 |
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
| 200 |
+
# refer to https://docs.cursor.com/context/ignore-files
|
| 201 |
+
.cursorignore
|
| 202 |
+
.cursorindexingignore
|
| 203 |
+
|
| 204 |
+
# Marimo
|
| 205 |
+
marimo/_static/
|
| 206 |
+
marimo/_lsp/
|
| 207 |
+
__marimo__/
|
Dockerfile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
WORKDIR /app
|
| 3 |
+
|
| 4 |
+
COPY requirements.txt .
|
| 5 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 6 |
+
|
| 7 |
+
COPY . .
|
| 8 |
+
|
| 9 |
+
RUN chmod -R 777 .
|
| 10 |
+
|
| 11 |
+
EXPOSE 7860
|
| 12 |
+
|
| 13 |
+
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Yousef Mohtady
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CorpGuideAI - HR Policy Assistant
|
| 2 |
+
|
| 3 |
+
**CorpGuideAI** is an advanced AI-powered assistant designed to help employees navigate and understand internal HR policies. Leveraging **Retrieval-Augmented Generation (RAG)**, it provides accurate, context-aware answers based on your organization's PDF documents.
|
| 4 |
+
|
| 5 |
+
## 🚀 Features
|
| 6 |
+
|
| 7 |
+
- **RAG Architecture**: Combines vector search with generative AI for precise answers.
|
| 8 |
+
- **LLM Integration**: Powered by **Groq** (using Llama models) for fast and efficient inference.
|
| 9 |
+
- **Vector Database**: Uses **ChromaDB** for efficient document storage and retrieval.
|
| 10 |
+
- **Smart Ingestion**:
|
| 11 |
+
- Extracts text from PDFs.
|
| 12 |
+
- Improves retrieval speed and accuracy with semantic chunking.
|
| 13 |
+
- Uses `Alibaba-NLP/gte-multilingual-base` for robust multilingual support.
|
| 14 |
+
- **Interactive Web UI**: A modern, clean chat interface to interact with the assistant.
|
| 15 |
+
- **Chat History Management**: Maintains context across the conversation session (Reset supported).
|
| 16 |
+
- **FastAPI Backend**: A high-performance API to serve requests.
|
| 17 |
+
- **Docker Support**: Containerized for easy deployment.
|
| 18 |
+
|
| 19 |
+
## 🛠️ Tech Stack
|
| 20 |
+
|
| 21 |
+
- **Language**: Python 3.10+
|
| 22 |
+
- **Frontend**: HTML5, CSS3, Vanilla JS
|
| 23 |
+
- **Backend**: FastAPI, Uvicorn
|
| 24 |
+
- **AI/ML**: LangChain, HuggingFace, ChromaDB, Groq
|
| 25 |
+
- **Tools**: `pypdf`, `sentence-transformers`, Docker
|
| 26 |
+
|
| 27 |
+
## 📂 Project Structure
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
CorpGuideAI-HR-Policy-Assistant/
|
| 31 |
+
├── api/
|
| 32 |
+
│ ├── main.py # FastAPI application & entry point
|
| 33 |
+
│ ├── schemas.py # Pydantic models
|
| 34 |
+
├── config/
|
| 35 |
+
│ ├── settings.py # Configuration settings
|
| 36 |
+
├── core/
|
| 37 |
+
│ ├── rag_pipeline.py # Core RAG logic & Chat History
|
| 38 |
+
│ ├── prompts.py # Prompt templates
|
| 39 |
+
├── data/ # PDF documents storage
|
| 40 |
+
├── services/
|
| 41 |
+
│ ├── document_processor.py
|
| 42 |
+
│ ├── vector_store.py
|
| 43 |
+
│ ├── llm_client.py
|
| 44 |
+
├── web_ui/ # Frontend Application
|
| 45 |
+
│ ├── index.html
|
| 46 |
+
│ ├── script.js
|
| 47 |
+
│ ├── style.css
|
| 48 |
+
├── ingest.py # Document ingestion script
|
| 49 |
+
├── Dockerfile # Docker container configuration
|
| 50 |
+
├── requirements.txt # Dependencies
|
| 51 |
+
└── README.md # Documentation
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
## ⚡ Prerequisites
|
| 55 |
+
|
| 56 |
+
- **Python 3.10+**
|
| 57 |
+
- **Groq API Key**: Get it from [Groq Console](https://console.groq.com/).
|
| 58 |
+
|
| 59 |
+
## 📦 Installation & Usage
|
| 60 |
+
|
| 61 |
+
### Option 1: Local Installation
|
| 62 |
+
|
| 63 |
+
1. **Clone & Setup**:
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
git clone <repository-url>
|
| 67 |
+
cd CorpGuideAI-HR-Policy-Assistant
|
| 68 |
+
python -m venv venv
|
| 69 |
+
|
| 70 |
+
# Windows
|
| 71 |
+
venv\Scripts\activate
|
| 72 |
+
# macOS/Linux
|
| 73 |
+
source venv/bin/activate
|
| 74 |
+
|
| 75 |
+
pip install -r requirements.txt
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
2. **Environment Variables**:
|
| 79 |
+
Create a `.env` file:
|
| 80 |
+
|
| 81 |
+
```env
|
| 82 |
+
GROQ_API_KEY=your_groq_api_key_here
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
3. **Ingest Documents**:
|
| 86 |
+
Place PDFs in `data/` and run:
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
python ingest.py
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
4. **Run Server**:
|
| 93 |
+
```bash
|
| 94 |
+
uvicorn api.main:app --reload
|
| 95 |
+
```
|
| 96 |
+
Access the Web UI at: `http://localhost:8000`
|
| 97 |
+
|
| 98 |
+
### Option 2: Docker
|
| 99 |
+
|
| 100 |
+
1. **Build Image**:
|
| 101 |
+
|
| 102 |
+
```bash
|
| 103 |
+
docker build -t corpguide-ai .
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
2. **Run Container**:
|
| 107 |
+
```bash
|
| 108 |
+
docker run -p 7860:7860 --env-file .env corpguide-ai
|
| 109 |
+
```
|
| 110 |
+
Access at: `http://localhost:7860`
|
| 111 |
+
|
| 112 |
+
## 🔗 API Endpoints
|
| 113 |
+
|
| 114 |
+
- `GET /`: Serves the Web UI.
|
| 115 |
+
- `POST /chat`: Chat endpoint.
|
| 116 |
+
- Body: `{ "question": "..." }` (History managed internally)
|
| 117 |
+
- `POST /reset`: Clears current chat history.
|
| 118 |
+
|
| 119 |
+
## 📄 License
|
| 120 |
+
|
| 121 |
+
MIT License - see [LICENSE](LICENSE) file.
|
api/__init__.py
ADDED
|
File without changes
|
api/main.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from fastapi import FastAPI, HTTPException
|
| 3 |
+
from contextlib import asynccontextmanager
|
| 4 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
+
from fastapi.staticfiles import StaticFiles
|
| 6 |
+
from fastapi.responses import FileResponse
|
| 7 |
+
from api.schemas import ChatRequest, ChatResponse, UploadResponse
|
| 8 |
+
from core.rag_pipeline import RagPipeline
|
| 9 |
+
|
| 10 |
+
logging.basicConfig(level=logging.INFO)
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
pipeline_resources = {}
|
| 14 |
+
|
| 15 |
+
@asynccontextmanager
|
| 16 |
+
async def lifespan(app: FastAPI):
|
| 17 |
+
logger.info("Starting CorpGuide AI API...")
|
| 18 |
+
try:
|
| 19 |
+
pipeline_resources["rag"] = RagPipeline()
|
| 20 |
+
logger.info("CorpGuide AI API ready for queries")
|
| 21 |
+
|
| 22 |
+
except Exception as e:
|
| 23 |
+
logger.error(f"Failed to initialize CorpGuide AI API: {str(e)}")
|
| 24 |
+
raise e
|
| 25 |
+
|
| 26 |
+
yield
|
| 27 |
+
|
| 28 |
+
pipeline_resources.clear()
|
| 29 |
+
logger.info("API shut down.")
|
| 30 |
+
|
| 31 |
+
app = FastAPI(
|
| 32 |
+
title="CorpGuide AI",
|
| 33 |
+
description="AI-powered HR policy assistant",
|
| 34 |
+
version="1.0.0",
|
| 35 |
+
lifespan=lifespan
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
app.add_middleware(
|
| 39 |
+
CORSMiddleware,
|
| 40 |
+
allow_origins=["*"],
|
| 41 |
+
allow_credentials=True,
|
| 42 |
+
allow_methods=["*"],
|
| 43 |
+
allow_headers=["*"],
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
app.mount("/static", StaticFiles(directory="web_ui"), name="static")
|
| 47 |
+
|
| 48 |
+
@app.get("/")
|
| 49 |
+
async def read_index():
|
| 50 |
+
return FileResponse("web_ui/index.html")
|
| 51 |
+
|
| 52 |
+
@app.post("/reset")
|
| 53 |
+
async def reset_chat():
|
| 54 |
+
try:
|
| 55 |
+
if 'rag' in pipeline_resources and hasattr(pipeline_resources['rag'], 'clear_history'):
|
| 56 |
+
pipeline_resources['rag'].clear_history()
|
| 57 |
+
return {"message": "Chat history has been reset."}
|
| 58 |
+
except Exception as e:
|
| 59 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 60 |
+
|
| 61 |
+
@app.post("/chat", response_model=ChatResponse)
|
| 62 |
+
async def chat(request: ChatRequest):
|
| 63 |
+
try:
|
| 64 |
+
if 'rag' not in pipeline_resources:
|
| 65 |
+
raise HTTPException(status_code=503, detail="System is initializing, try again later")
|
| 66 |
+
|
| 67 |
+
rag = pipeline_resources['rag']
|
| 68 |
+
|
| 69 |
+
result = rag.process_query(
|
| 70 |
+
question= request.question,
|
| 71 |
+
chat_history=request.chat_history
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
return ChatResponse(
|
| 75 |
+
answer=result["answer"],
|
| 76 |
+
sources=result["sources"],
|
| 77 |
+
latency=result["latency"]
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
except Exception as e:
|
| 81 |
+
logger.error(f"Error in chat: {str(e)}")
|
| 82 |
+
raise HTTPException(status_code=500, detail=str(e))
|
api/schemas.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
from typing import List, Optional, Tuple
|
| 3 |
+
|
| 4 |
+
class ChatRequest(BaseModel):
|
| 5 |
+
question: str
|
| 6 |
+
chat_history: List[Tuple[str,str]] = []
|
| 7 |
+
|
| 8 |
+
class ChatResponse(BaseModel):
|
| 9 |
+
answer:str
|
| 10 |
+
sources: List[str]
|
| 11 |
+
latency: Optional[float] = None
|
| 12 |
+
|
| 13 |
+
class UploadResponse(BaseModel):
|
| 14 |
+
filename: str
|
| 15 |
+
chunks_count: int
|
| 16 |
+
message:str
|
chroma_db/6a26d0e5-feba-4f8b-a92a-f8d1220144dc/data_level0.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c7e2a5c66a30e0d9228b85d06681048e2d25425ad5b7f8f10b672c87ac37e001
|
| 3 |
+
size 321200
|
chroma_db/6a26d0e5-feba-4f8b-a92a-f8d1220144dc/header.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:03cb3ac86f3e5bcb15e88b9bf99f760ec6b33e31d64a699e129b49868db6d733
|
| 3 |
+
size 100
|
chroma_db/6a26d0e5-feba-4f8b-a92a-f8d1220144dc/length.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ff6c03ed65fe1386e0c057caca3ac3771497dc92063f9713f93abdf9b6399b2a
|
| 3 |
+
size 400
|
chroma_db/6a26d0e5-feba-4f8b-a92a-f8d1220144dc/link_lists.bin
ADDED
|
File without changes
|
chroma_db/chroma.sqlite3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d49857011870b48f6c378045d8d713fd1dac413de0a216d3003022472d80ea7f
|
| 3 |
+
size 1146880
|
config/__init.py
ADDED
|
File without changes
|
config/settings.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
|
| 4 |
+
load_dotenv()
|
| 5 |
+
|
| 6 |
+
class Settings:
|
| 7 |
+
#API Key
|
| 8 |
+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 9 |
+
if not GROQ_API_KEY:
|
| 10 |
+
raise ValueError("GROQ_API_KEY is not set in the environment variables")
|
| 11 |
+
|
| 12 |
+
#Models
|
| 13 |
+
LLM_MODEL = "meta-llama/llama-4-scout-17b-16e-instruct"
|
| 14 |
+
EMBEDDING_MODEL = "Alibaba-NLP/gte-multilingual-base"
|
| 15 |
+
|
| 16 |
+
#Vector DB
|
| 17 |
+
CHROMA_PERSIST_DIR = "chroma_db"
|
| 18 |
+
COLLECTION_NAME = "policy_docs"
|
| 19 |
+
DATA_DIR = "data"
|
| 20 |
+
|
| 21 |
+
settings = Settings()
|
core/__init.py
ADDED
|
File without changes
|
core/prompts.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
| 2 |
+
|
| 3 |
+
SYSTEM_TEMPLATE = """
|
| 4 |
+
You are CorpGuide AI, an expert HR and Policy Assistant.
|
| 5 |
+
Your task is to answer the user's question based ONLY on the provided context below.
|
| 6 |
+
|
| 7 |
+
<context>
|
| 8 |
+
{context}
|
| 9 |
+
</context>
|
| 10 |
+
|
| 11 |
+
Guidelines:
|
| 12 |
+
1. If the answer is not in the context, strictly reply: "I'm sorry, I cannot find this information in the company policy documents."
|
| 13 |
+
2. Do not make up or hallucinate information.
|
| 14 |
+
3. Keep your answer professional, concise, and helpful.
|
| 15 |
+
4. If the question is in Arabic, answer in Arabic. If in English, answer in English.
|
| 16 |
+
5. Provide specific details (numbers, days, penalties) if available in the context.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
CONTEXTUALIZE_Q_SYSTEM_PROMPT = """
|
| 20 |
+
Given a chat history and the latest user question which might reference context in the chat history,
|
| 21 |
+
formulate a standalone question which can be understood without the chat history.
|
| 22 |
+
Do NOT answer the question, just reformulate it if needed and otherwise return it as is.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def get_chat_prompt():
|
| 26 |
+
return ChatPromptTemplate.from_messages([
|
| 27 |
+
("system", SYSTEM_TEMPLATE),
|
| 28 |
+
MessagesPlaceholder("chat_history"),
|
| 29 |
+
("user", "{input}")
|
| 30 |
+
])
|
| 31 |
+
|
| 32 |
+
def get_contextualize_prompt():
|
| 33 |
+
return ChatPromptTemplate.from_messages([
|
| 34 |
+
("system", CONTEXTUALIZE_Q_SYSTEM_PROMPT),
|
| 35 |
+
MessagesPlaceholder("chat_history"),
|
| 36 |
+
("user", "{input}")
|
| 37 |
+
])
|
core/rag_pipeline.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import time
|
| 3 |
+
from langchain_core.messages import HumanMessage, AIMessage
|
| 4 |
+
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
|
| 5 |
+
from langchain.chains.combine_documents import create_stuff_documents_chain
|
| 6 |
+
from config.settings import Settings
|
| 7 |
+
from services.llm_client import LLMClient
|
| 8 |
+
from services.vector_store import VectorStore
|
| 9 |
+
from core.prompts import get_chat_prompt, get_contextualize_prompt
|
| 10 |
+
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
class RagPipeline:
|
| 14 |
+
def __init__(self):
|
| 15 |
+
try:
|
| 16 |
+
self.llm = LLMClient().get_llm()
|
| 17 |
+
self.vector_store = VectorStore()
|
| 18 |
+
self.retriever = self.vector_store.get_retriever(k=5)
|
| 19 |
+
self.prompt = get_chat_prompt()
|
| 20 |
+
self.history_aware_retriever = create_history_aware_retriever(
|
| 21 |
+
self.llm,
|
| 22 |
+
self.retriever,
|
| 23 |
+
get_contextualize_prompt()
|
| 24 |
+
)
|
| 25 |
+
self.question_answer_chain = create_stuff_documents_chain(
|
| 26 |
+
self.llm,
|
| 27 |
+
get_chat_prompt()
|
| 28 |
+
)
|
| 29 |
+
self.rag_chain = create_retrieval_chain(
|
| 30 |
+
self.history_aware_retriever,
|
| 31 |
+
self.question_answer_chain
|
| 32 |
+
)
|
| 33 |
+
self.chat_history = []
|
| 34 |
+
logger.info("RAG pipeline initialized successfully")
|
| 35 |
+
|
| 36 |
+
except Exception as e:
|
| 37 |
+
logger.error(f"Error initializing RAG pipeline: {str(e)}")
|
| 38 |
+
raise e
|
| 39 |
+
|
| 40 |
+
def clear_history(self):
|
| 41 |
+
self.chat_history = []
|
| 42 |
+
logger.info("Chat history cleared")
|
| 43 |
+
|
| 44 |
+
def process_query(self, question:str, chat_history: list = []):
|
| 45 |
+
start_time = time.time()
|
| 46 |
+
try:
|
| 47 |
+
logger.info(f"Processing query: {question}")
|
| 48 |
+
response = self.rag_chain.invoke({
|
| 49 |
+
"input": question,
|
| 50 |
+
"chat_history": self.chat_history
|
| 51 |
+
})
|
| 52 |
+
|
| 53 |
+
self.chat_history.extend([
|
| 54 |
+
HumanMessage(content=question),
|
| 55 |
+
AIMessage(content=response["answer"])
|
| 56 |
+
])
|
| 57 |
+
|
| 58 |
+
latency = time.time() - start_time
|
| 59 |
+
|
| 60 |
+
source_files = list(set(
|
| 61 |
+
[doc.metadata.get("source", "Unknown") for doc in response["context"]]
|
| 62 |
+
))
|
| 63 |
+
|
| 64 |
+
return{
|
| 65 |
+
"answer": response["answer"],
|
| 66 |
+
"sources": source_files,
|
| 67 |
+
"latency": latency
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
except Exception as e:
|
| 71 |
+
logger.error(f"Error processing query: {str(e)}")
|
| 72 |
+
raise e
|
data/employee_handbook_-_2024.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:60d7587505554f258c5758378243a21c4e0fa2484e730eee4c0e58aa245fde8c
|
| 3 |
+
size 314218
|
ingest.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
import shutil
|
| 4 |
+
from services.document_processor import DocumentProcessor
|
| 5 |
+
from services.vector_store import VectorStore
|
| 6 |
+
from config.settings import Settings
|
| 7 |
+
|
| 8 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s- %(levelname)s - %(message)s')
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
def main():
|
| 12 |
+
logger.info("Starting document ingestion process")
|
| 13 |
+
|
| 14 |
+
if os.path.exists(Settings.CHROMA_PERSIST_DIR):
|
| 15 |
+
logger.warning(f"Removing existing database at {Settings.CHROMA_PERSIST_DIR}")
|
| 16 |
+
shutil.rmtree(Settings.CHROMA_PERSIST_DIR)
|
| 17 |
+
|
| 18 |
+
processor = DocumentProcessor()
|
| 19 |
+
vector_store = VectorStore()
|
| 20 |
+
|
| 21 |
+
if not os.path.exists(Settings.DATA_DIR):
|
| 22 |
+
os.makedirs(Settings.DATA_DIR)
|
| 23 |
+
logger.info(f"Data directory '{Settings.DATA_DIR}' not found. Created it. Please add PDFs there.")
|
| 24 |
+
return
|
| 25 |
+
|
| 26 |
+
pdf_files = [f for f in os.listdir(Settings.DATA_DIR) if f.endswith('.pdf')]
|
| 27 |
+
|
| 28 |
+
if not pdf_files:
|
| 29 |
+
logger.warning("No PDF files found in the data directory. Please add PDFs there.")
|
| 30 |
+
return
|
| 31 |
+
|
| 32 |
+
total_chunks = 0
|
| 33 |
+
|
| 34 |
+
for pdf_file in pdf_files:
|
| 35 |
+
file_path = os.path.join(Settings.DATA_DIR, pdf_file)
|
| 36 |
+
logger.info(f"Processing: {pdf_file}...")
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
chunks = processor.process_pdf(file_path)
|
| 40 |
+
|
| 41 |
+
for chunk in chunks:
|
| 42 |
+
chunk.metadata['source'] = pdf_file
|
| 43 |
+
|
| 44 |
+
vector_store.add_documents(chunks)
|
| 45 |
+
total_chunks += len(chunks)
|
| 46 |
+
logger.info(f"Processed {len(chunks)} chunks from {pdf_file}")
|
| 47 |
+
|
| 48 |
+
except Exception as e:
|
| 49 |
+
logger .error(f"Failed to process {pdf_file}: {str(e)}")
|
| 50 |
+
continue
|
| 51 |
+
|
| 52 |
+
logger.info(f"Ingestion Completed. Total chunks stored: {total_chunks}")
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
main()
|
| 56 |
+
|
requirements.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.124.4
|
| 2 |
+
uvicorn==0.38.0
|
| 3 |
+
python-multipart==0.0.20
|
| 4 |
+
python-dotenv==1.2.1
|
| 5 |
+
langchain==0.3.0
|
| 6 |
+
langchain-groq==0.2.0
|
| 7 |
+
langchain-huggingface==0.1.0
|
| 8 |
+
langchain-chroma==1.1.0
|
| 9 |
+
langchain-community==0.3.0
|
| 10 |
+
langchain-experimental==0.4.1
|
| 11 |
+
chromadb==1.3.7
|
| 12 |
+
pypdf==6.4.1
|
| 13 |
+
sentence-transformers==5.2.0
|
| 14 |
+
aiofiles==24.1.0
|
services/__init.py
ADDED
|
File without changes
|
services/document_processor.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from typing import List
|
| 3 |
+
from langchain_core.documents import Document
|
| 4 |
+
from langchain_community.document_loaders import PyPDFLoader
|
| 5 |
+
from langchain_experimental.text_splitter import SemanticChunker
|
| 6 |
+
from langchain_huggingface import HuggingFaceEmbeddings
|
| 7 |
+
from config.settings import Settings
|
| 8 |
+
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
logging.basicConfig(level=logging.INFO)
|
| 11 |
+
|
| 12 |
+
class DocumentProcessor:
|
| 13 |
+
def __init__(self):
|
| 14 |
+
try:
|
| 15 |
+
logger.info(f"Loading embeddings model: {Settings.EMBEDDING_MODEL}")
|
| 16 |
+
|
| 17 |
+
# Initialize embeddings
|
| 18 |
+
self.embeddings = HuggingFaceEmbeddings(
|
| 19 |
+
model_name = Settings.EMBEDDING_MODEL,
|
| 20 |
+
model_kwargs = {"trust_remote_code": True}
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
# Initialize text Semantic splitter
|
| 24 |
+
self.text_splitter = SemanticChunker(
|
| 25 |
+
embeddings = self.embeddings,
|
| 26 |
+
breakpoint_threshold_type = "percentile"
|
| 27 |
+
)
|
| 28 |
+
logger.info("DocumentProcessor initialized successfully")
|
| 29 |
+
|
| 30 |
+
except Exception as e:
|
| 31 |
+
logger.error(f"Error initializing DocumentProcessor")
|
| 32 |
+
raise e
|
| 33 |
+
|
| 34 |
+
def process_pdf(self, file_path:str):
|
| 35 |
+
try:
|
| 36 |
+
logger.info(f"Processing file: {file_path}")
|
| 37 |
+
|
| 38 |
+
# Read PDF
|
| 39 |
+
loader = PyPDFLoader(file_path)
|
| 40 |
+
raw_documents = loader.load()
|
| 41 |
+
logger.info(f"Loaded {len(raw_documents)} pages from PDF.")
|
| 42 |
+
|
| 43 |
+
# Split text
|
| 44 |
+
chunks = self.text_splitter.split_documents(raw_documents)
|
| 45 |
+
logger.info(f"Created {len(chunks)} semantic chunks.")
|
| 46 |
+
|
| 47 |
+
return chunks
|
| 48 |
+
|
| 49 |
+
except Exception as e:
|
| 50 |
+
logger.error(f"Error processing PDF: {str(e)}")
|
| 51 |
+
raise e
|
services/llm_client.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from langchain_groq import ChatGroq
|
| 3 |
+
from config.settings import Settings
|
| 4 |
+
|
| 5 |
+
logger = logging.getLogger(__name__)
|
| 6 |
+
|
| 7 |
+
class LLMClient:
|
| 8 |
+
def __init__(self):
|
| 9 |
+
try:
|
| 10 |
+
logger.info(f"Initializing Groq LLM with model: {Settings.LLM_MODEL}")
|
| 11 |
+
|
| 12 |
+
self.llm = ChatGroq(
|
| 13 |
+
api_key = Settings.GROQ_API_KEY,
|
| 14 |
+
model_name = Settings.LLM_MODEL,
|
| 15 |
+
temperature = 0.0,
|
| 16 |
+
max_retries = 2,
|
| 17 |
+
streaming = True
|
| 18 |
+
)
|
| 19 |
+
logger.info("LLM initialized successfully")
|
| 20 |
+
|
| 21 |
+
except Exception as e:
|
| 22 |
+
logger.error(f"Failed to initialize LLM Client: {str(e)}")
|
| 23 |
+
raise e
|
| 24 |
+
|
| 25 |
+
def get_llm(self):
|
| 26 |
+
return self.llm
|
services/vector_store.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
import shutil
|
| 4 |
+
from typing import List
|
| 5 |
+
from langchain_core.documents import Document
|
| 6 |
+
from langchain_chroma import Chroma
|
| 7 |
+
from langchain_huggingface import HuggingFaceEmbeddings
|
| 8 |
+
from config.settings import Settings
|
| 9 |
+
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
class VectorStore:
|
| 13 |
+
def __init__(self):
|
| 14 |
+
try:
|
| 15 |
+
self.embeddings = HuggingFaceEmbeddings(
|
| 16 |
+
model_name = Settings.EMBEDDING_MODEL,
|
| 17 |
+
model_kwargs = {"trust_remote_code": True}
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
self.vector_db = Chroma(
|
| 21 |
+
persist_directory = Settings.CHROMA_PERSIST_DIR,
|
| 22 |
+
embedding_function = self.embeddings,
|
| 23 |
+
collection_name = Settings.COLLECTION_NAME
|
| 24 |
+
)
|
| 25 |
+
logger.info(f"VectorStore connected to {Settings.CHROMA_PERSIST_DIR}")
|
| 26 |
+
|
| 27 |
+
except Exception as e:
|
| 28 |
+
logger.error(f"Failed to initialize VectorStore: {str(e)}")
|
| 29 |
+
raise e
|
| 30 |
+
|
| 31 |
+
def add_documents(self, documents: List[Document]):
|
| 32 |
+
try:
|
| 33 |
+
if not documents:
|
| 34 |
+
logger.warning("No documents provided to add to the VectorStore")
|
| 35 |
+
return
|
| 36 |
+
|
| 37 |
+
logger.info(f"Adding {len(documents)} documents to ChromaDB...")
|
| 38 |
+
self.vector_db.add_documents(documents)
|
| 39 |
+
logger.info("Documents added to ChromaDB successfully")
|
| 40 |
+
|
| 41 |
+
except Exception as e:
|
| 42 |
+
logger.error(f"Error adding documents: {str(e)}")
|
| 43 |
+
raise e
|
| 44 |
+
|
| 45 |
+
def get_retriever(self, k: int = 5):
|
| 46 |
+
return self.vector_db.as_retriever(
|
| 47 |
+
search_type = "similarity",
|
| 48 |
+
search_kwargs = {"k": k}
|
| 49 |
+
)
|
utils/__init.py
ADDED
|
File without changes
|
utils/vision_helper.py
ADDED
|
File without changes
|
web_ui/index.html
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" dir="ltr">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>CorpGuide AI Assistant</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/style.css" />
|
| 8 |
+
<link
|
| 9 |
+
rel="stylesheet"
|
| 10 |
+
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
|
| 11 |
+
/>
|
| 12 |
+
</head>
|
| 13 |
+
<body>
|
| 14 |
+
<div class="chat-container">
|
| 15 |
+
<div class="chat-header">
|
| 16 |
+
<div class="logo">
|
| 17 |
+
<i class="fa-solid fa-robot"></i>
|
| 18 |
+
<span>CorpGuide AI</span>
|
| 19 |
+
</div>
|
| 20 |
+
<button
|
| 21 |
+
onclick="startNewChat()"
|
| 22 |
+
class="new-chat-btn"
|
| 23 |
+
title="Start New Chat"
|
| 24 |
+
>
|
| 25 |
+
<i class="fa-solid fa-arrows-rotate"></i>
|
| 26 |
+
</button>
|
| 27 |
+
<div class="status-dot"></div>
|
| 28 |
+
</div>
|
| 29 |
+
|
| 30 |
+
<div class="chat-box" id="chat-box">
|
| 31 |
+
<div class="message bot-message">
|
| 32 |
+
<div class="msg-content">
|
| 33 |
+
Hi, I am CorpGuide AI. Ask me any question about your company's
|
| 34 |
+
policies.
|
| 35 |
+
</div>
|
| 36 |
+
</div>
|
| 37 |
+
</div>
|
| 38 |
+
|
| 39 |
+
<div class="input-area">
|
| 40 |
+
<input
|
| 41 |
+
type="text"
|
| 42 |
+
id="user-input"
|
| 43 |
+
placeholder="Ask me any question about your company's policies."
|
| 44 |
+
autocomplete="off"
|
| 45 |
+
/>
|
| 46 |
+
<button onclick="sendMessage()" id="send-btn">
|
| 47 |
+
<i class="fa-solid fa-paper-plane"></i>
|
| 48 |
+
</button>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
|
| 52 |
+
<script src="/static/script.js"></script>
|
| 53 |
+
</body>
|
| 54 |
+
</html>
|
web_ui/script.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ==========================================
|
| 2 |
+
// Connection and Memory Settings
|
| 3 |
+
// ==========================================
|
| 4 |
+
|
| 5 |
+
// Relative URL to work both locally and on server
|
| 6 |
+
const API_URL = "/chat";
|
| 7 |
+
const RESET_URL = "/reset";
|
| 8 |
+
|
| 9 |
+
// Array to store chat history (so the model remembers context)
|
| 10 |
+
let chatHistory = [];
|
| 11 |
+
|
| 12 |
+
// Define page elements
|
| 13 |
+
const chatBox = document.getElementById('chat-box');
|
| 14 |
+
const userInput = document.getElementById('user-input');
|
| 15 |
+
const sendBtn = document.getElementById('send-btn');
|
| 16 |
+
|
| 17 |
+
// ==========================================
|
| 18 |
+
// Core Functions
|
| 19 |
+
// ==========================================
|
| 20 |
+
|
| 21 |
+
/**
|
| 22 |
+
* Function to send message and handle API interaction
|
| 23 |
+
*/
|
| 24 |
+
async function sendMessage() {
|
| 25 |
+
const question = userInput.value.trim();
|
| 26 |
+
|
| 27 |
+
// If no text, do nothing
|
| 28 |
+
if (!question) return;
|
| 29 |
+
|
| 30 |
+
// 1. Display user message in chat immediately
|
| 31 |
+
appendMessage(question, 'user');
|
| 32 |
+
userInput.value = ''; // Clear input
|
| 33 |
+
sendBtn.disabled = true; // Disable button to prevent double sending
|
| 34 |
+
|
| 35 |
+
// 2. Display "typing..." indicator
|
| 36 |
+
const loadingId = appendLoading();
|
| 37 |
+
|
| 38 |
+
try {
|
| 39 |
+
// 3. Prepare payload (question + old history)
|
| 40 |
+
const payload = {
|
| 41 |
+
question: question,
|
| 42 |
+
chat_history: chatHistory
|
| 43 |
+
};
|
| 44 |
+
|
| 45 |
+
// 4. Connect to server
|
| 46 |
+
const response = await fetch(API_URL, {
|
| 47 |
+
method: 'POST',
|
| 48 |
+
headers: { 'Content-Type': 'application/json' },
|
| 49 |
+
body: JSON.stringify(payload)
|
| 50 |
+
});
|
| 51 |
+
|
| 52 |
+
if (!response.ok) {
|
| 53 |
+
throw new Error(`Server Error: ${response.status}`);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
const data = await response.json();
|
| 57 |
+
|
| 58 |
+
// 5. Remove loading indicator and show AI response
|
| 59 |
+
removeLoading(loadingId);
|
| 60 |
+
appendMessage(data.answer, 'bot', data.sources);
|
| 61 |
+
|
| 62 |
+
// 6. Update memory (for next question)
|
| 63 |
+
// Add question and answer to the list
|
| 64 |
+
chatHistory.push(["human", question]);
|
| 65 |
+
chatHistory.push(["ai", data.answer]);
|
| 66 |
+
|
| 67 |
+
} catch (error) {
|
| 68 |
+
console.error("Error:", error);
|
| 69 |
+
removeLoading(loadingId);
|
| 70 |
+
appendMessage("Sorry, an error occurred connecting to the server. Please ensure the backend is running. 😔", 'bot');
|
| 71 |
+
} finally {
|
| 72 |
+
// Re-enable button and focus input
|
| 73 |
+
sendBtn.disabled = false;
|
| 74 |
+
userInput.focus();
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
/**
|
| 79 |
+
* Function to start a new chat (Reset)
|
| 80 |
+
*/
|
| 81 |
+
async function startNewChat() {
|
| 82 |
+
// 1. Reset browser memory
|
| 83 |
+
chatHistory = [];
|
| 84 |
+
|
| 85 |
+
// 2. Clear screen (return to initial state)
|
| 86 |
+
chatBox.innerHTML = `
|
| 87 |
+
<div class="message bot-message">
|
| 88 |
+
<div class="msg-content">
|
| 89 |
+
Welcome back! 👋<br>Memory cleared, you can start a new topic.
|
| 90 |
+
</div>
|
| 91 |
+
</div>
|
| 92 |
+
`;
|
| 93 |
+
|
| 94 |
+
// 3. Notify server to clear memory (optional, for double confirmation)
|
| 95 |
+
try {
|
| 96 |
+
await fetch(RESET_URL, { method: 'POST' });
|
| 97 |
+
console.log("Backend history reset.");
|
| 98 |
+
} catch (e) {
|
| 99 |
+
console.warn("Backend reset failed (might be stateless):", e);
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
// ==========================================
|
| 104 |
+
// UI Helpers
|
| 105 |
+
// ==========================================
|
| 106 |
+
|
| 107 |
+
/**
|
| 108 |
+
* Add message to screen
|
| 109 |
+
* @param {string} text - Message text
|
| 110 |
+
* @param {string} sender - Sender ('user' or 'bot')
|
| 111 |
+
* @param {Array} sources - List of sources (optional)
|
| 112 |
+
*/
|
| 113 |
+
function appendMessage(text, sender, sources = []) {
|
| 114 |
+
const msgDiv = document.createElement('div');
|
| 115 |
+
msgDiv.classList.add('message', sender === 'user' ? 'user-message' : 'bot-message');
|
| 116 |
+
|
| 117 |
+
// Convert newlines to <br> for proper formatting
|
| 118 |
+
// Could use a library like 'marked' for full Markdown, but this is simpler
|
| 119 |
+
let formattedText = text.replace(/\n/g, '<br>');
|
| 120 |
+
|
| 121 |
+
// Convert text between ** ** to bold tags (simple implementation)
|
| 122 |
+
formattedText = formattedText.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
| 123 |
+
|
| 124 |
+
let html = `<div class="msg-content">${formattedText}</div>`;
|
| 125 |
+
|
| 126 |
+
/*
|
| 127 |
+
// If there are sources, add them in a small box below - DISABLED BY REQUEST
|
| 128 |
+
if (sources && sources.length > 0) {
|
| 129 |
+
// Remove duplicates from filenames
|
| 130 |
+
const uniqueSources = [...new Set(sources)];
|
| 131 |
+
html += `<div class="sources-box">📚 Sources: ${uniqueSources.join(', ')}</div>`;
|
| 132 |
+
}
|
| 133 |
+
*/
|
| 134 |
+
|
| 135 |
+
msgDiv.innerHTML = html;
|
| 136 |
+
chatBox.appendChild(msgDiv);
|
| 137 |
+
scrollToBottom();
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
/**
|
| 141 |
+
* Add loading indicator (3 moving dots)
|
| 142 |
+
*/
|
| 143 |
+
function appendLoading() {
|
| 144 |
+
const id = 'loading-' + Date.now();
|
| 145 |
+
const msgDiv = document.createElement('div');
|
| 146 |
+
msgDiv.classList.add('message', 'bot-message');
|
| 147 |
+
msgDiv.id = id;
|
| 148 |
+
msgDiv.innerHTML = `
|
| 149 |
+
<div class="msg-content">
|
| 150 |
+
<div class="typing-indicator">
|
| 151 |
+
<span></span><span></span><span></span>
|
| 152 |
+
</div>
|
| 153 |
+
</div>`;
|
| 154 |
+
chatBox.appendChild(msgDiv);
|
| 155 |
+
scrollToBottom();
|
| 156 |
+
return id;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
/**
|
| 160 |
+
* Remove loading indicator
|
| 161 |
+
*/
|
| 162 |
+
function removeLoading(id) {
|
| 163 |
+
const element = document.getElementById(id);
|
| 164 |
+
if (element) element.remove();
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
/**
|
| 168 |
+
* Scroll to bottom of chat automatically
|
| 169 |
+
*/
|
| 170 |
+
function scrollToBottom() {
|
| 171 |
+
chatBox.scrollTop = chatBox.scrollHeight;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
// ==========================================
|
| 175 |
+
// Event Listeners
|
| 176 |
+
// ==========================================
|
| 177 |
+
|
| 178 |
+
// When send button is clicked
|
| 179 |
+
sendBtn.addEventListener('click', sendMessage);
|
| 180 |
+
|
| 181 |
+
// When Enter is pressed in input box
|
| 182 |
+
userInput.addEventListener('keypress', (e) => {
|
| 183 |
+
if (e.key === 'Enter') {
|
| 184 |
+
sendMessage();
|
| 185 |
+
}
|
| 186 |
+
});
|
| 187 |
+
|
| 188 |
+
// Focus on input box when page loads
|
| 189 |
+
window.onload = () => userInput.focus();
|
web_ui/style.css
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 3 |
+
background-color: #f0f2f5;
|
| 4 |
+
margin: 0;
|
| 5 |
+
display: flex;
|
| 6 |
+
justify-content: center;
|
| 7 |
+
align-items: center;
|
| 8 |
+
height: 100vh;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
.chat-container {
|
| 12 |
+
width: 100%;
|
| 13 |
+
max-width: 500px; /* Suitable size for mobile and desktop */
|
| 14 |
+
background: white;
|
| 15 |
+
height: 90vh;
|
| 16 |
+
border-radius: 15px;
|
| 17 |
+
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
|
| 18 |
+
display: flex;
|
| 19 |
+
flex-direction: column;
|
| 20 |
+
overflow: hidden;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
.chat-header {
|
| 24 |
+
background: #2c3e50;
|
| 25 |
+
color: white;
|
| 26 |
+
padding: 15px;
|
| 27 |
+
display: flex;
|
| 28 |
+
align-items: center;
|
| 29 |
+
justify-content: space-between;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.new-chat-btn {
|
| 33 |
+
background: transparent;
|
| 34 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 35 |
+
color: white;
|
| 36 |
+
width: 35px;
|
| 37 |
+
height: 35px;
|
| 38 |
+
border-radius: 50%;
|
| 39 |
+
cursor: pointer;
|
| 40 |
+
display: flex;
|
| 41 |
+
align-items: center;
|
| 42 |
+
justify-content: center;
|
| 43 |
+
transition: all 0.3s ease;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.new-chat-btn:hover {
|
| 47 |
+
background: rgba(255, 255, 255, 0.2);
|
| 48 |
+
transform: rotate(180deg); /* Cool rotation effect on hover */
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.logo { font-size: 1.2rem; font-weight: bold; }
|
| 52 |
+
.logo i { margin-left: 10px; color: #3498db; }
|
| 53 |
+
.status-dot { width: 10px; height: 10px; background: #2ecc71; border-radius: 50%; }
|
| 54 |
+
|
| 55 |
+
.chat-box {
|
| 56 |
+
flex: 1;
|
| 57 |
+
padding: 20px;
|
| 58 |
+
overflow-y: auto;
|
| 59 |
+
background: #f9f9f9;
|
| 60 |
+
display: flex;
|
| 61 |
+
flex-direction: column;
|
| 62 |
+
gap: 15px;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.message { display: flex; flex-direction: column; max-width: 80%; }
|
| 66 |
+
.bot-message { align-self: flex-start; }
|
| 67 |
+
.user-message { align-self: flex-end; }
|
| 68 |
+
|
| 69 |
+
.msg-content {
|
| 70 |
+
padding: 12px 16px;
|
| 71 |
+
border-radius: 15px;
|
| 72 |
+
font-size: 0.95rem;
|
| 73 |
+
line-height: 1.5;
|
| 74 |
+
position: relative;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.bot-message .msg-content {
|
| 78 |
+
background: #e9ecef;
|
| 79 |
+
color: #333;
|
| 80 |
+
border-bottom-right-radius: 2px;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
.user-message .msg-content {
|
| 84 |
+
background: #3498db;
|
| 85 |
+
color: white;
|
| 86 |
+
border-bottom-left-radius: 2px;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.sources-box {
|
| 90 |
+
font-size: 0.8rem;
|
| 91 |
+
color: #666;
|
| 92 |
+
margin-top: 5px;
|
| 93 |
+
background: #fff;
|
| 94 |
+
padding: 5px 10px;
|
| 95 |
+
border-radius: 5px;
|
| 96 |
+
border: 1px solid #ddd;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.input-area {
|
| 100 |
+
padding: 15px;
|
| 101 |
+
background: white;
|
| 102 |
+
border-top: 1px solid #eee;
|
| 103 |
+
display: flex;
|
| 104 |
+
gap: 10px;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
input {
|
| 108 |
+
flex: 1;
|
| 109 |
+
padding: 12px;
|
| 110 |
+
border: 1px solid #ddd;
|
| 111 |
+
border-radius: 25px;
|
| 112 |
+
outline: none;
|
| 113 |
+
font-family: inherit;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
button {
|
| 117 |
+
background: #2c3e50;
|
| 118 |
+
color: white;
|
| 119 |
+
border: none;
|
| 120 |
+
width: 45px;
|
| 121 |
+
height: 45px;
|
| 122 |
+
border-radius: 50%;
|
| 123 |
+
cursor: pointer;
|
| 124 |
+
transition: 0.2s;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
button:hover { background: #34495e; }
|
| 128 |
+
button:disabled { background: #ccc; cursor: not-allowed; }
|
| 129 |
+
|
| 130 |
+
/* Loading Animation */
|
| 131 |
+
.typing-indicator span {
|
| 132 |
+
display: inline-block;
|
| 133 |
+
width: 6px; height: 6px;
|
| 134 |
+
background-color: #555;
|
| 135 |
+
border-radius: 50%;
|
| 136 |
+
animation: typing 1s infinite;
|
| 137 |
+
margin: 0 2px;
|
| 138 |
+
}
|
| 139 |
+
@keyframes typing { 0% {opacity: 0.3} 50% {opacity: 1} 100% {opacity: 0.3} }
|