Al1Abdullah commited on
Commit
6256536
·
0 Parent(s):

Initial clean deploy without binary files

Browse files
.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # API Keys and Secrets
2
+ .env
3
+
4
+ # Python Environment
5
+ botenv/
6
+ venv/
7
+ __pycache__/
8
+ *.pyc
9
+
10
+ # OS files
11
+ .DS_Store
12
+ Thumbs.db
Dockerfile ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+ WORKDIR /code
3
+ COPY ./app/requirements.txt /code/requirements.txt
4
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
5
+ COPY . .
6
+ CMD ["uvicorn", "app.index:app", "--host", "0.0.0.0", "--port", "7860"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ali Abdullah
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,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # <img src="app/static/atomcamp_logo.png" alt="atomcamp logo" height="40" align="center"> RAG Chatbot
2
+
3
+ This is a Retrieval-Augmented Generation (RAG) chatbot I built to serve as a factual representative for **atomcamp** programs, courses, and admissions. Instead of relying on general AI knowledge, this system uses a **FastAPI** backend and **LangChain** to retrieve verified data from a **Qdrant** vector database, ensuring every response from the **GPT-OSS-120B** model is grounded in actual company information. By processing website data through **Hugging Face** embeddings and using a **Maximal Marginal Relevance (MMR)** retrieval strategy, the bot provides accurate, non-robotic answers without the "hallucinations" typical of standard AI.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. Project Overview
8
+ 2. Interface Preview
9
+ 3. Core Technical Features
10
+ 4. System Architecture
11
+ 5. Tech Stack Specifications
12
+ 6. Installation and Environment Setup
13
+ 7. Configuration and API Integration
14
+ 8. Project Directory Structure
15
+ 9. Execution Workflow
16
+ 10. Key Component Breakdown
17
+ 11. Advanced UI/UX Implementation
18
+ 12. Status and Versioning
19
+
20
+ -----
21
+
22
+ ## Project Overview
23
+
24
+ The primary objective of this project is to eliminate the "robotic" nature of traditional AI assistants. By implementing a sophisticated RAG pipeline, the system grounds every response in verified atomcamp data. The architecture is designed for low latency, high relevance, and a professional user experience through a bespoke web interface that supports dynamic theme switching and responsive data rendering.
25
+
26
+ -----
27
+
28
+ ## Interface Preview
29
+
30
+ The platform provides a professional-grade interface with a custom-built theme toggle to support various user environments.
31
+
32
+ | Light Mode Interface | Dark Mode Interface |
33
+ | :--- | :--- |
34
+ | ![Light Mode Interface](image.png) | ![Dark Mode Interface](image2.png) |
35
+
36
+ -----
37
+
38
+ ## Core Technical Features
39
+
40
+ * Official Representative Persona: The system is programmed with a custom prompt that forces the LLM to "own" the knowledge, speaking as an authoritative human representative rather than a machine reading a file.
41
+ * High-Speed Inference: Powered by the Groq LPU (Language Processing Unit) engine using the GPT-OSS-120B model for near-instantaneous responses.
42
+ * Cloud-Native Vector Search: Utilizes a managed Qdrant collection for high-dimensional semantic search and retrieval.
43
+ * Maximal Marginal Relevance (MMR): A specialized retrieval strategy that balances document relevance with information diversity to provide more comprehensive answers.
44
+ * Dynamic Dark Mode: A fully integrated CSS variable system that swaps entire color palettes, including high-contrast link colors (Orange in Dark Mode) for maximum accessibility.
45
+ * Auto-Expanding Interface: The input section utilizes an intelligent vertical-growth textarea that expands as the user types complex inquiries.
46
+ * Markdown Integration: Full support for professional text formatting, including bold terms and standard bulleted lists.
47
+
48
+ -----
49
+
50
+ ## System Architecture
51
+
52
+ ### The RAG Pipeline
53
+
54
+ 1. Data Ingestion: The system crawls the official atomcamp web domain, extracts core content using BeautifulSoup4, and splits it into semantic chunks.
55
+ 2. Embedding Generation: Chunks are converted into 768-dimensional vectors using the Hugging Face all-mpnet-base-v2 model.
56
+ 3. Vector Storage: Vectors are stored in a Qdrant Cloud collection with full metadata support.
57
+ 4. User Query: The user submits a question through the FastAPI web interface.
58
+ 5. Semantic Retrieval: The system performs a similarity search in Qdrant, retrieving the top contexts while applying MMR to reduce redundancy.
59
+ 6. Contextual Synthesis: The LLM processes the retrieved context, user question, and conversation history to generate a natural, authoritative response.
60
+
61
+ -----
62
+
63
+ ## Tech Stack Specifications
64
+
65
+ ### Backend and Orchestration
66
+
67
+ * Framework: FastAPI (v0.105.0) for high-performance API routing.
68
+ * Orchestrator: LangChain (v0.3.0) for managing the RAG chain and memory.
69
+ * Web Server: Uvicorn (v0.34.0) for asynchronous execution.
70
+
71
+ ### Artificial Intelligence
72
+
73
+ * Inference Engine: Groq.
74
+ * Model: openai/gpt-oss-120b.
75
+ * Embeddings: sentence-transformers/all-mpnet-base-v2 via Hugging Face.
76
+
77
+ ### Data and Storage
78
+
79
+ * Vector DB: Qdrant Cloud.
80
+ * Web Scraping: BeautifulSoup4 and WebBaseLoader.
81
+
82
+ -----
83
+
84
+ ## Installation and Environment Setup
85
+
86
+ ### 1\. Repository Initialization
87
+
88
+ Clone the project and enter the application directory:
89
+
90
+ ```bash
91
+ git clone <your-repository-url>
92
+ cd RAG-Based-Chatbot-main/app
93
+ ```
94
+
95
+ ### 2\. Virtual Environment Creation
96
+
97
+ It is highly recommended to use a virtual environment to manage dependencies:
98
+
99
+ ```bash
100
+ python -m venv botenv
101
+ # On Windows:
102
+ botenv\Scripts\activate
103
+ # On macOS/Linux:
104
+ source botenv/bin/activate
105
+ ```
106
+
107
+ ### 3\. Dependency Installation
108
+
109
+ Install the required packages listed in the requirements file:
110
+
111
+ ```bash
112
+ pip install -r requirements.txt
113
+ ```
114
+
115
+ -----
116
+
117
+ ## Configuration and API Integration
118
+
119
+ The system requires an environment file to manage secure credentials. Create a file named `.env` in the `app/` directory:
120
+
121
+ ```env
122
+ # Hugging Face Access Token
123
+ HF_TOKEN=your_huggingface_token
124
+
125
+ # Groq Cloud API Key
126
+ GROQ_API_KEY=your_groq_api_key
127
+
128
+ # Qdrant Cloud Credentials
129
+ QDRANT_API_KEY=your_qdrant_api_key
130
+ QDRANT_URL=your_qdrant_cloud_url
131
+ ```
132
+
133
+ -----
134
+
135
+ ## Project Directory Structure
136
+
137
+ The project follows a modular "Zero-Hurdle" root structure to ensure all paths are resolved correctly during execution:
138
+
139
+ ```text
140
+ app/
141
+ ├── main.py # FastAPI server and API entry point
142
+ ├── chain.py # RAG logic and conversation memory
143
+ ├── ingest.py # Data crawling and vector ingestion
144
+ ├── .env # Private API keys
145
+ ├── requirements.txt # Project dependencies
146
+ ├── Prompt/
147
+ │ └── Prompt.py # Custom representative persona
148
+ ├── LLM/
149
+ │ └── LLM.py # Groq model configuration
150
+ ├── VectorStores/
151
+ │ └── Vectorstores.py # Qdrant cloud connection logic
152
+ ├── embeddings/
153
+ │ └── embedding.py # Hugging Face model setup
154
+ ├── config/
155
+ │ └── config.py # Environment variable loader
156
+ ├── static/ # Professional UI/UX assets
157
+ │ ├── css/
158
+ │ │ └── style.css # Theme and layout styling
159
+ │ ├── js/
160
+ │ │ └── chat.js # Interaction and animation logic
161
+ │ ├── templates/
162
+ │ │ └── index.html # Web structure
163
+ │ └── atomcamp_logo.png # Official organization logo
164
+ └── Extras/ # If Document and Other resource
165
+ ```
166
+
167
+ -----
168
+
169
+ ## Execution Workflow
170
+
171
+ ### Step 1: Knowledge Base Ingestion
172
+
173
+ Before running the chatbot, you must populate the vector database with atomcamp's latest information:
174
+
175
+ ```bash
176
+ python ingest.py
177
+ ```
178
+
179
+ ### Step 2: Launching the Platform
180
+
181
+ Start the FastAPI server to initialize the RAG chain and the web interface:
182
+
183
+ ```bash
184
+ python main.py
185
+ ```
186
+
187
+ ### Step 3: Accessing the UI
188
+
189
+ Open your browser and navigate to the following address:
190
+ `http://localhost:8000`
191
+
192
+ -----
193
+
194
+ ## Key Component Breakdown
195
+
196
+ ### Advanced Prompt Engineering (Prompt.py)
197
+
198
+ The core of the system's intelligence lies in its specialized persona. The prompt explicitly forbids the use of robotic disclaimers like "According to the context" or "The information provided state." It instructs the AI to combine duplicate facts into sophisticated, high-intelligence sentences and strictly enforce the lowercase **atomcamp** branding.
199
+
200
+ ### Retrieval Logic (chain.py)
201
+
202
+ The system utilizes a `RunnableParallel` architecture. When a question is received, the chain simultaneously retrieves relevant documents from Qdrant, pulls the last several turns of conversation from memory, and prepares the final prompt for the LLM. This parallel execution minimizes response latency.
203
+
204
+ -----
205
+
206
+ ## Advanced UI/UX Implementation
207
+
208
+ * Professional Input Handling: The input bar is a custom textarea that supports `Enter` to send and `Shift + Enter` for new lines, behaving like a modern enterprise communication tool.
209
+ * Vertical Container Stability: The chat bubbles use advanced word-break rules to ensure that long strings of code or characters never break the horizontal container width.
210
+ * Thematic Link Management: Links are styled with a dynamic CSS variable (`--link-color`) that switches to a high-contrast orange in dark mode, ensuring email addresses and URLs are always readable.
211
+
212
+ -----
213
+
214
+ ## Status and Versioning
215
+
216
+ * Version: 1.5.0
217
+ * Last Updated: July 2025
218
+ * Status: Production Ready
219
+ * Organization: atomcamp Official
app/LLM/LLM.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_groq import ChatGroq
2
+ from config.config import GROQ_API_KEY
3
+
4
+ llm = ChatGroq(
5
+ model="openai/gpt-oss-120b",
6
+ temperature=1.0,
7
+ max_tokens=None,
8
+ reasoning_format="parsed",
9
+ timeout=None,
10
+ max_retries=2,
11
+ api_key=GROQ_API_KEY,
12
+ )
app/Prompt/Prompt.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.prompts import ChatPromptTemplate
2
+
3
+ prompt = ChatPromptTemplate.from_template("""
4
+ You are a highly intelligent and confident Official Representative of atomcamp.
5
+
6
+ CRITICAL GUIDELINES:
7
+ 1. BRANDING: Always use "atomcamp" (lowercase 'a') when referring to the organization.
8
+ 2. NO ROBOTIC META-TALK: Never use phrases like "based on the provided context," "according to the information," or "the context does not mention."
9
+ 3. OWN THE KNOWLEDGE: Answer as if you inherently know this information. Use "We," "Our," and "The program is."
10
+ 4. NO REPETITION: Combine duplicate facts into one clear, smart sentence.
11
+ 5. FALLBACK: If the information is genuinely missing, simply say you don't have the specific details on that right now and suggest they contact the team at atomcamp.com.
12
+
13
+ Internal Knowledge Base:
14
+ {context}
15
+
16
+ Conversation History:
17
+ {chat_history}
18
+
19
+ User Inquiry:
20
+ {question}
21
+
22
+ Answer confidently, naturally, and professionally as a human representative would.
23
+ """)
app/VectorStores/Vectorstores.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_qdrant import QdrantVectorStore
2
+ from embeddings.embedding import get_embedding
3
+ from config.config import QDRANT_URL, QDRANT_API_KEY # Use config variables
4
+ from dotenv import load_dotenv
5
+ load_dotenv()
6
+
7
+ def get_vectorstore():
8
+ return QdrantVectorStore.from_existing_collection(
9
+ embedding=get_embedding(),
10
+ collection_name="Ninesol_Technologies_Knowledge_Base",
11
+ prefer_grpc=True,
12
+ url=QDRANT_URL,
13
+ api_key=QDRANT_API_KEY
14
+ )
app/chain.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.runnables import RunnableParallel, RunnableLambda, RunnablePassthrough
2
+ from langchain_core.output_parsers import StrOutputParser
3
+ from langchain_core.messages import get_buffer_string
4
+ from langchain_classic.memory import ConversationBufferMemory
5
+ from VectorStores.Vectorstores import get_vectorstore
6
+ from LLM.LLM import llm
7
+ from Prompt.Prompt import prompt
8
+
9
+ def join_docs(docs):
10
+ return "\n\n".join(doc.page_content for doc in docs)
11
+
12
+ memory = ConversationBufferMemory(
13
+ return_messages=True,
14
+ memory_key="chat_history"
15
+ )
16
+
17
+ def create_rag_chain(memory):
18
+ retriever = get_vectorstore().as_retriever(
19
+ search_type="mmr",
20
+ search_kwargs={"k": 3, "fetch_k": 10}
21
+ )
22
+
23
+ return (
24
+ RunnableParallel({
25
+ "context": retriever | RunnableLambda(join_docs),
26
+ "question": RunnablePassthrough(),
27
+ "chat_history": RunnableLambda(
28
+ lambda _: get_buffer_string(memory.chat_memory.messages)
29
+ )
30
+ })
31
+ | prompt
32
+ | llm
33
+
34
+ )
35
+
36
+ chain=create_rag_chain(memory)
37
+ # while True:
38
+ # user_input = input("Enter your query (or 'exit' to quit): ")
39
+ # print("User:", user_input)
40
+ # if user_input.lower() in ['q', 'exit']:
41
+ # break
42
+
43
+ # # .invoke() returns the final state dictionary directly
44
+ # result = create_rag_chain(memory).invoke(
45
+ # user_input
46
+ # )
47
+ # memory.chat_memory.add_user_message(user_input)
48
+ # memory.chat_memory.add_ai_message(result)
49
+ # AI_msg=result
50
+ # print("Bot:", AI_msg)
51
+
app/config/config.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import os
3
+
4
+ load_dotenv()
5
+
6
+ QDRANT_URL = os.getenv("QDRANT_URL")
7
+ QDRANT_API_KEY = os.getenv("QDRANT_API_KEY")
8
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
9
+ HUGGINGFACEHUB_API_TOKEN = os.getenv("HUGGINGFACEHUB_API_TOKEN")
app/embeddings/embedding.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_huggingface import HuggingFaceEndpointEmbeddings
2
+ from dotenv import load_dotenv
3
+ from config.config import HUGGINGFACEHUB_API_TOKEN
4
+ load_dotenv()
5
+
6
+ def get_embedding():
7
+ embeddings = HuggingFaceEndpointEmbeddings(
8
+ repo_id="sentence-transformers/all-mpnet-base-v2",
9
+ task="feature-extraction",
10
+ huggingfacehub_api_token=HUGGINGFACEHUB_API_TOKEN
11
+ )
12
+ return embeddings
13
+
14
+
15
+ # vector = embedding().embed_query("This is a cloud-based embedding.")
16
+ # print(vector)
app/index.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from pydantic import BaseModel
3
+ from chain import chain, memory # Direct import for simple root structure
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ from fastapi.responses import HTMLResponse
6
+ from fastapi.staticfiles import StaticFiles
7
+ import os
8
+
9
+ app = FastAPI()
10
+
11
+ app.add_middleware(
12
+ CORSMiddleware,
13
+ allow_origins=["*"],
14
+ allow_methods=["*"],
15
+ allow_headers=["*"],
16
+ )
17
+
18
+ class ChatRequest(BaseModel):
19
+ message: str
20
+
21
+ @app.post("/chat")
22
+ async def chat_endpoint(request: ChatRequest):
23
+ # Asynchronous response for better performance and responsiveness
24
+ response = await chain.ainvoke(request.message)
25
+
26
+ # Update conversation history manually for RAG context
27
+ memory.chat_memory.add_user_message(request.message)
28
+ memory.chat_memory.add_ai_message(response.content)
29
+
30
+ return {"response": response.content}
31
+
32
+ # Mount the static folder to serve CSS, JS, and the atomcamp logo
33
+ if os.path.exists("static"):
34
+ app.mount("/static", StaticFiles(directory="static"), name="static")
35
+
36
+ @app.get("/", response_class=HTMLResponse)
37
+ async def read_root():
38
+ # Serves the index.html from your organized static/templates directory
39
+ file_path = os.path.join("static", "templates", "index.html")
40
+
41
+ # Check for file existence to avoid internal server errors
42
+ if not os.path.exists(file_path):
43
+ return HTMLResponse(content=f"UI Error: File not found at {file_path}", status_code=404)
44
+
45
+ with open(file_path, "r") as f:
46
+ return f.read()
47
+
48
+ if __name__ == "__main__":
49
+ import uvicorn
50
+ # The server runs on localhost:8000
51
+ uvicorn.run(app, host="0.0.0.0", port=8000)
app/ingest.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import bs4 # Make sure to 'pip install beautifulsoup4'
3
+ from langchain_community.document_loaders import WebBaseLoader
4
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
5
+ from langchain_qdrant import QdrantVectorStore
6
+ from embeddings.embedding import get_embedding
7
+ from config.config import QDRANT_URL, QDRANT_API_KEY
8
+
9
+ # 1. Smarter Loading: Only grab the 'main' content to prevent repeating
10
+ # headers, footers, and menus in every single chunk.
11
+ loader = WebBaseLoader(
12
+ web_paths=[
13
+ "https://atomcamp.com/",
14
+ "https://www.atomcamp.com/course/",
15
+ "https://www.atomcamp.com/about-us/",
16
+ "https://www.atomcamp.com/events/",
17
+ "https://www.atomcamp.com/blogs/",
18
+ "https://www.atomcamp.com/news/",
19
+ "https://www.atomcamp.com/webinars/",
20
+ "https://www.atomcamp.com/publications/",
21
+ "https://www.atomcamp.com/ai-solutions/"
22
+ ],
23
+ bs_kwargs=dict(
24
+ parse_only=bs4.SoupStrainer(["main", "article", "h1", "h2", "p"])
25
+ )
26
+ )
27
+
28
+ data = loader.load()
29
+
30
+ # 2. Split into clean chunks
31
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
32
+ docs = text_splitter.split_documents(data)
33
+
34
+ # 3. Safe Upload to Cloud
35
+ BATCH_SIZE = 20
36
+
37
+ print(f"Cleaning old data and starting ingestion of {len(docs)} documents...")
38
+
39
+ # NOTE: Using .from_documents will ADD to the collection.
40
+ # If you want to fix the repetition from previous runs,
41
+ # you should delete the collection in your Qdrant Dashboard first.
42
+ qdrant = QdrantVectorStore.from_documents(
43
+ docs,
44
+ get_embedding(),
45
+ url=QDRANT_URL,
46
+ api_key=QDRANT_API_KEY,
47
+ collection_name="atomcamp_knowledge_base",
48
+ batch_size=BATCH_SIZE,
49
+ timeout=120
50
+ )
51
+
52
+ print("Ingestion complete! atomcamp knowledge base is clean and ready.")
app/requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ langchain==0.3.0
2
+ langchain-core==0.3.0
3
+ langchain-groq==0.2.0
4
+ langchain-huggingface==0.1.0
5
+ langchain-qdrant==0.2.0
6
+ qdrant-client==1.12.0
7
+ streamlit==1.40.2
8
+ python-dotenv==1.0.1
9
+ huggingface-hub==0.23.0
app/static/atomcamp_logo.png ADDED
app/static/css/style.css ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* 1. Global Variables & Themes */
2
+ :root {
3
+ --primary: #166534;
4
+ --accent: #22c55e;
5
+ --bg-body: #f1f5f9;
6
+ --bg-app: #ffffff;
7
+ --bg-chat: linear-gradient(to bottom, #ffffff, #f9fafb);
8
+ --bg-input: #f8fafc;
9
+ --text-main: #1e293b;
10
+ --text-light: #64748b;
11
+ --bot-bubble: #f1f5f9;
12
+ --border: #e2e8f0;
13
+ --icon-color: #64748b;
14
+ --link-color: #0d00ff; /* Professional Blue for Light Mode */
15
+ }
16
+
17
+ body.dark-mode {
18
+ --bg-body: #0f172a;
19
+ --bg-app: #1e293b;
20
+ --bg-chat: #1e293b;
21
+ --bg-input: #0f172a;
22
+ --text-main: #f8fafc;
23
+ --text-light: #94a3b8;
24
+ --bot-bubble: #334155;
25
+ --border: #334155;
26
+ --icon-color: #f8fafc;
27
+ --link-color: #ffa321; /* High-Visibility Orange for Dark Mode */
28
+ }
29
+
30
+ /* 2. Global Reset */
31
+ * {
32
+ box-sizing: border-box;
33
+ margin: 0;
34
+ padding: 0;
35
+ transition: background 0.3s ease, color 0.3s ease, border-color 0.3s ease;
36
+ }
37
+
38
+ body {
39
+ font-family: 'Inter', sans-serif;
40
+ background: var(--bg-body);
41
+ color: var(--text-main);
42
+ display: flex;
43
+ height: 100vh;
44
+ justify-content: center;
45
+ overflow: hidden;
46
+ }
47
+
48
+ /* 3. Main Layout */
49
+ .app-container {
50
+ width: 100%;
51
+ max-width: 850px;
52
+ height: 96vh;
53
+ margin-top: 2vh;
54
+ display: flex;
55
+ flex-direction: column;
56
+ background: var(--bg-app);
57
+ border-radius: 24px;
58
+ box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
59
+ position: relative;
60
+ }
61
+
62
+ header {
63
+ padding: 1.5rem;
64
+ border-bottom: 1px solid var(--border);
65
+ display: flex;
66
+ flex-direction: column;
67
+ align-items: center;
68
+ gap: 8px;
69
+ position: relative;
70
+ }
71
+
72
+ .logo-img {
73
+ height: 35px;
74
+ width: auto;
75
+ object-fit: contain;
76
+ }
77
+
78
+ #theme-toggle {
79
+ position: absolute;
80
+ top: 1.5rem;
81
+ right: 1.5rem;
82
+ background: none;
83
+ border: none;
84
+ cursor: pointer;
85
+ width: 40px;
86
+ height: 40px;
87
+ border-radius: 12px;
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ color: var(--icon-color);
92
+ }
93
+
94
+ #theme-toggle:hover { background: var(--bg-body); }
95
+ #theme-toggle svg { width: 22px; height: 22px; stroke-width: 2; }
96
+
97
+ /* 4. Status Badge */
98
+ .status-badge {
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 6px;
102
+ padding: 4px 12px;
103
+ background: rgba(16, 185, 129, 0.1);
104
+ border-radius: 20px;
105
+ }
106
+
107
+ .status-dot { width: 6px; height: 6px; border-radius: 50%; }
108
+ .status-text {
109
+ font-size: 0.65rem;
110
+ font-weight: 700;
111
+ text-transform: uppercase;
112
+ letter-spacing: 0.05em;
113
+ }
114
+
115
+ .online .status-dot {
116
+ background: var(--accent);
117
+ box-shadow: 0 0 8px var(--accent);
118
+ }
119
+ .online .status-text { color: var(--accent); }
120
+
121
+ .typing .status-dot {
122
+ background: var(--text-light);
123
+ animation: pulse 1s infinite;
124
+ }
125
+ .typing .status-text { color: var(--text-light); }
126
+
127
+ /* 5. Chat History & Bubbles */
128
+ #chat-box {
129
+ flex: 1;
130
+ overflow-y: auto;
131
+ overflow-x: hidden;
132
+ padding: 2.5rem;
133
+ display: flex;
134
+ flex-direction: column;
135
+ gap: 1.5rem;
136
+ background: var(--bg-chat);
137
+ }
138
+
139
+ .message-row {
140
+ display: flex;
141
+ gap: 12px;
142
+ width: 100%;
143
+ max-width: 85%;
144
+ animation: slideUp 0.4s ease;
145
+ }
146
+
147
+ .message-row.user {
148
+ align-self: flex-end;
149
+ flex-direction: row-reverse;
150
+ }
151
+
152
+ .bubble {
153
+ padding: 1rem 1.25rem;
154
+ border-radius: 18px;
155
+ line-height: 1.6;
156
+ font-size: 0.95rem;
157
+ word-wrap: break-word;
158
+ overflow-wrap: break-word;
159
+ word-break: break-word;
160
+ }
161
+
162
+ /* Fix for Link Visibility */
163
+ .bubble a {
164
+ color: var(--link-color);
165
+ text-decoration: underline;
166
+ font-weight: 500;
167
+ transition: color 0.3s ease;
168
+ }
169
+
170
+ .bot .bubble {
171
+ background: var(--bot-bubble);
172
+ border: 1px solid var(--border);
173
+ border-top-left-radius: 4px;
174
+ color: var(--text-main);
175
+ }
176
+
177
+ .user .bubble {
178
+ background: var(--primary);
179
+ color: white;
180
+ border-top-right-radius: 4px;
181
+ box-shadow: 0 4px 12px rgba(22, 101, 52, 0.15);
182
+ }
183
+
184
+ .bubble ul { list-style-type: disc; margin: 10px 0 10px 20px; }
185
+ .bubble li { margin-bottom: 6px; }
186
+
187
+ /* 6. Typing Animations */
188
+ .loader { display: flex; gap: 4px; padding: 6px 0; }
189
+ .dot {
190
+ width: 5px;
191
+ height: 5px;
192
+ background: var(--text-light);
193
+ border-radius: 50%;
194
+ animation: bounce 1.4s infinite ease-in-out both;
195
+ }
196
+ .dot:nth-child(1) { animation-delay: -0.32s; }
197
+ .dot:nth-child(2) { animation-delay: -0.16s; }
198
+
199
+ /* 7. Input Section */
200
+ .input-wrapper {
201
+ padding: 1.5rem 2.5rem;
202
+ background: var(--bg-app);
203
+ border-top: 1px solid var(--border);
204
+ }
205
+
206
+ .input-box {
207
+ display: flex;
208
+ align-items: flex-end;
209
+ background: var(--bg-input);
210
+ border-radius: 16px;
211
+ padding: 8px 12px;
212
+ border: 1.5px solid var(--border);
213
+ transition: 0.3s;
214
+ min-height: 56px;
215
+ }
216
+
217
+ .input-box:focus-within { border-color: var(--primary); }
218
+
219
+ #user-input {
220
+ flex: 1;
221
+ border: none;
222
+ background: transparent;
223
+ padding: 10px 4px;
224
+ outline: none;
225
+ font-size: 0.95rem;
226
+ color: var(--text-main);
227
+ font-family: inherit;
228
+ resize: none;
229
+ max-height: 150px;
230
+ overflow-y: auto;
231
+ line-height: 1.5;
232
+ }
233
+
234
+ button#send-btn {
235
+ background: var(--primary);
236
+ color: white;
237
+ border: none;
238
+ padding: 12px 24px;
239
+ border-radius: 12px;
240
+ cursor: pointer;
241
+ font-weight: 600;
242
+ margin-left: 8px;
243
+ margin-bottom: 4px;
244
+ }
245
+
246
+ /* 8. Keyframes */
247
+ @keyframes slideUp { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
248
+ @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } }
249
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
250
+
251
+ /* Scrollbar Styling */
252
+ ::-webkit-scrollbar { width: 5px; }
253
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 10px; }
app/static/js/chat.js ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 1. Elements Selection
2
+ const box = document.getElementById('chat-box');
3
+ const input = document.getElementById('user-input');
4
+ const btn = document.getElementById('send-btn');
5
+ const statusBadge = document.getElementById('status-badge');
6
+ const statusText = document.getElementById('status-text');
7
+ const themeToggle = document.getElementById('theme-toggle');
8
+
9
+ // 2. Professional SVG Icons
10
+ const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>`;
11
+ const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>`;
12
+
13
+ // 3. Theme Toggle Logic
14
+ function updateToggleIcon(isDark) {
15
+ themeToggle.innerHTML = isDark ? moonIcon : sunIcon;
16
+ }
17
+
18
+ themeToggle.onclick = () => {
19
+ document.body.classList.toggle('dark-mode');
20
+ updateToggleIcon(document.body.classList.contains('dark-mode'));
21
+ };
22
+
23
+ // 4. Input Auto-Resize Logic
24
+ input.addEventListener('input', function() {
25
+ this.style.height = 'auto';
26
+ this.style.height = (this.scrollHeight) + 'px';
27
+ });
28
+
29
+ // 5. Messaging Logic
30
+ async function sendMessage() {
31
+ const msg = input.value.trim();
32
+ if (!msg) return;
33
+
34
+ // UI Reset
35
+ input.value = '';
36
+ input.style.height = 'auto';
37
+ addMessage(msg, 'user');
38
+
39
+ // Show "Typing..." Status
40
+ statusBadge.className = 'status-badge typing';
41
+ statusText.innerText = 'Typing...';
42
+
43
+ // Add Loading Animation Row
44
+ const loaderId = 'loader-' + Date.now();
45
+ const loaderRow = document.createElement('div');
46
+ loaderRow.className = 'message-row bot';
47
+ loaderRow.id = loaderId;
48
+ loaderRow.innerHTML = `<div class="bubble"><div class="loader"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div></div>`;
49
+ box.appendChild(loaderRow);
50
+ box.scrollTo({ top: box.scrollHeight, behavior: 'smooth' });
51
+
52
+ try {
53
+ const res = await fetch('/chat', {
54
+ method: 'POST',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify({ message: msg })
57
+ });
58
+ const data = await res.json();
59
+
60
+ // Remove loader and display bot response
61
+ document.getElementById(loaderId).remove();
62
+ addMessage(data.response, 'bot');
63
+ } catch (e) {
64
+ if (document.getElementById(loaderId)) document.getElementById(loaderId).remove();
65
+ addMessage("Sorry, I'm having trouble connecting to the server.", 'bot');
66
+ } finally {
67
+ // Restore "Online" status
68
+ statusBadge.className = 'status-badge online';
69
+ statusText.innerText = 'Online';
70
+ }
71
+ }
72
+
73
+ function addMessage(content, role) {
74
+ const div = document.createElement('div');
75
+ div.className = `message-row ${role}`;
76
+ // Use marked.js for professional Markdown rendering in bot responses
77
+ const html = role === 'bot' ? marked.parse(content) : content;
78
+ div.innerHTML = `<div class="bubble">${html}</div>`;
79
+ box.appendChild(div);
80
+ box.scrollTo({ top: box.scrollHeight, behavior: 'smooth' });
81
+ }
82
+
83
+ // 6. Event Listeners
84
+ btn.onclick = sendMessage;
85
+
86
+ input.onkeydown = (e) => {
87
+ // Enter sends message, Shift+Enter adds new line
88
+ if (e.key === 'Enter' && !e.shiftKey) {
89
+ e.preventDefault();
90
+ sendMessage();
91
+ }
92
+ };
93
+
94
+ // 7. Initial State
95
+ updateToggleIcon(false);
96
+ // Pre-parse the initial welcome message
97
+ const initialBotMsg = box.querySelector('.bot .bubble');
98
+ if (initialBotMsg) initialBotMsg.innerHTML = marked.parse(initialBotMsg.innerHTML);
app/static/templates/index.html ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>atomcamp AI</title>
7
+ <link rel="stylesheet" href="/static/css/style.css">
8
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
+ </head>
10
+ <body>
11
+ <div class="app-container">
12
+ <header>
13
+ <img src="/static/atomcamp_logo.png" alt="atomcamp" class="logo-img">
14
+
15
+ <button id="theme-toggle" title="Toggle Theme"></button>
16
+
17
+ <div id="status-badge" class="status-badge online">
18
+ <span class="status-dot"></span>
19
+ <span id="status-text" class="status-text">Online</span>
20
+ </div>
21
+ </header>
22
+
23
+ <div id="chat-box">
24
+ <div class="message-row bot">
25
+ <div class="bubble">Hello! I'm the **atomcamp** AI. How can I assist you with our programs today?</div>
26
+ </div>
27
+ </div>
28
+
29
+ <div class="input-wrapper">
30
+ <div class="input-box">
31
+ <textarea id="user-input" placeholder="Type a message..." autocomplete="off" rows="1"></textarea>
32
+ <button id="send-btn">Send</button>
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ <script src="/static/js/chat.js"></script>
38
+ </body>
39
+ </html>