d-e-e-k-11 commited on
Commit
cbe0a7a
·
verified ·
1 Parent(s): 6398483

Upload folder using huggingface_hub

Browse files
.env ADDED
@@ -0,0 +1 @@
 
 
1
+ GOOGLE_API_KEY=AIzaSyAwFDQXJD3DWkCthwE1EQO9VC3ctrTcrPQ
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ 7817_1.csv filter=lfs diff=lfs merge=lfs -text
37
+ chroma_db/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
7817_1.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:92cf56b1520f970d37d237d0e4f393efffbdab14ba15d52b14e8868f7d5eb3ba
3
+ size 18386219
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ COPY requirements.txt .
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ COPY . .
14
+
15
+ # Ensure the vector store and data are present
16
+ # preprocessed_docs.json and chroma_db/ should be part of the build
17
+
18
+ EXPOSE 7860
19
+
20
+ # We'll use gunicorn for the Space
21
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md CHANGED
@@ -1,10 +1,34 @@
1
- ---
2
- title: Retail Product Assistant
3
- emoji: 📈
4
- colorFrom: pink
5
- colorTo: pink
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Retail Product Knowledge Assistant (RAG Model)
2
+
3
+ This project builds a Retrieval-Augmented Generation (RAG) model using retail product data (Kindle reviews and details). It uses a vector database to store product information and an LLM to answer questions naturally.
4
+
5
+ ## Tech Stack
6
+ - **LLM**: Google Gemini (via `langchain-google-genai`)
7
+ - **Embeddings**: HuggingFace (`all-MiniLM-L6-v2`)
8
+ - **Vector Store**: ChromaDB
9
+ - **Framework**: LangChain
10
+
11
+ ## Setup
12
+ 1. **API Key**: Add your Google Gemini API Key to the `.env` file:
13
+ ```env
14
+ GOOGLE_API_KEY=your_actual_key_here
15
+ ```
16
+ 2. **Build Knowledge Base**:
17
+ Run the following command to process the data and build the vector database:
18
+ ```bash
19
+ python main.py
20
+ ```
21
+ (It will automatically detect if the database needs to be built).
22
+
23
+ ## Usage
24
+ Run `main.py` and ask questions about the products, such as:
25
+ - "Which Kindle model has the best resolution?"
26
+ - "What do users say about the battery life of the Paperwhite?"
27
+ - "Is the Kindle Voyage worth the extra money?"
28
+
29
+ ## Files
30
+ - `7817_1.csv`: Raw product data.
31
+ - `preprocess.py`: Cleans and formats data into JSON.
32
+ - `rag_model.py`: Contains the logic for the RAG pipeline.
33
+ - `main.py`: Interactive CLI for user queries.
34
+ - `chroma_db/`: Directory where the vector store is persisted.
app.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ from rag_model import get_qa_chain, build_rag_system, PERSIST_DIRECTORY, HuggingFaceEmbeddings, Chroma
3
+ from dotenv import load_dotenv
4
+ import os
5
+ load_dotenv(override=True)
6
+ print(f"App: API Key loaded (starting with {os.getenv('GOOGLE_API_KEY', 'None')[:5]}...)")
7
+
8
+ app = Flask(__name__)
9
+
10
+ # Load or Build Vector Store
11
+ if not os.path.exists(PERSIST_DIRECTORY):
12
+ print("Vector store not found. Building...")
13
+ vectorstore = build_rag_system()
14
+ else:
15
+ print("Loading existing vector store...")
16
+ embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
17
+ vectorstore = Chroma(persist_directory=PERSIST_DIRECTORY, embedding_function=embeddings)
18
+
19
+ qa_chain = get_qa_chain(vectorstore)
20
+
21
+ @app.route('/')
22
+ def index():
23
+ return render_template('index.html')
24
+
25
+ @app.route('/ask', methods=['POST'])
26
+ def ask():
27
+ data = request.json
28
+ query = data.get('query')
29
+ print(f"Processing query: {query}")
30
+
31
+ if not query:
32
+ return jsonify({"error": "No query provided"}), 400
33
+
34
+ if not qa_chain:
35
+ return jsonify({"error": "QA chain not initialized. Check GOOGLE_API_KEY."}), 500
36
+
37
+ try:
38
+ result = qa_chain({"query": query})
39
+ answer = result['result']
40
+ sources = []
41
+ seen = set()
42
+ for doc in result['source_documents']:
43
+ source_name = doc.metadata.get('name', 'Unknown')
44
+ if source_name not in seen:
45
+ sources.append(source_name)
46
+ seen.add(source_name)
47
+
48
+ return jsonify({
49
+ "answer": answer,
50
+ "sources": sources[:3] # Return top 3 unique names
51
+ })
52
+ except Exception as e:
53
+ return jsonify({"error": str(e)}), 500
54
+
55
+ if __name__ == '__main__':
56
+ app.run(debug=True, port=5000)
chroma_db/73f185a9-e985-49ef-8c16-8c9acf109315/data_level0.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:97636bb963ce51b1990b3b4290dfe132e5277521604170865da20675fd900624
3
+ size 5328004
chroma_db/73f185a9-e985-49ef-8c16-8c9acf109315/header.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6194ad38681db82ca87feaaa099b3e8791f8ded2e54d1241e9719b8a6a18257d
3
+ size 100
chroma_db/73f185a9-e985-49ef-8c16-8c9acf109315/index_metadata.pickle ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:def0c5e5c125cba2afce37ddc75157a85026da9bc703e60fbada61868841bfcd
3
+ size 292608
chroma_db/73f185a9-e985-49ef-8c16-8c9acf109315/length.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:657bb227bf74711a4e2f80351342c7aa997ce5fa6cfc277b922045d277e649ca
3
+ size 12716
chroma_db/73f185a9-e985-49ef-8c16-8c9acf109315/link_lists.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:62e9fa8b0f27e850d27b6db438cf3b7fb6a3fbf0607eef1452e5afd77845972a
3
+ size 26996
chroma_db/chroma.sqlite3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f519a0f46127579cddc14f876ca632155b6ab254c22c1f746d7e14bdc677d66d
3
+ size 32440320
main.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from rag_model import get_qa_chain, HuggingFaceEmbeddings, Chroma
2
+ import os
3
+
4
+ def main():
5
+ PERSIST_DIRECTORY = "chroma_db"
6
+
7
+ if not os.path.exists(PERSIST_DIRECTORY):
8
+ print("Knowledge base not built. Building now...")
9
+ from rag_model import build_rag_system
10
+ vectorstore = build_rag_system()
11
+ else:
12
+ print("Loading existing knowledge base...")
13
+ embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
14
+ vectorstore = Chroma(persist_directory=PERSIST_DIRECTORY, embedding_function=embeddings)
15
+
16
+ qa_chain = get_qa_chain(vectorstore)
17
+
18
+ if not qa_chain:
19
+ print("Failed to initialize QA chain. Check your GOOGLE_API_KEY in .env")
20
+ return
21
+
22
+ print("\n--- Retail Product Knowledge Assistant ---")
23
+ print("Type 'exit' to quit.")
24
+
25
+ while True:
26
+ query = input("\nAsk a question about our products: ")
27
+ if query.lower() == 'exit':
28
+ break
29
+
30
+ print("Thinking...")
31
+ try:
32
+ result = qa_chain({"query": query})
33
+ print(f"\nAnswer: {result['result']}")
34
+ print("\nSources (Top Reviews):")
35
+ seen_sources = set()
36
+ for doc in result["source_documents"]:
37
+ source_info = f"{doc.metadata['name']} (Brand: {doc.metadata['brand']})"
38
+ if source_info not in seen_sources:
39
+ print(f"- {source_info}")
40
+ seen_sources.add(source_info)
41
+ except Exception as e:
42
+ print(f"An error occurred: {e}")
43
+
44
+ if __name__ == "__main__":
45
+ main()
preprocess.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import json
3
+
4
+ def preprocess_data(file_path):
5
+ # Load the CSV
6
+ df = pd.read_csv(file_path)
7
+
8
+ # Select relevant columns
9
+ # name, brand, categories, reviews.text, reviews.title, reviews.rating
10
+ relevant_cols = ['name', 'brand', 'categories', 'reviews.text', 'reviews.title', 'reviews.rating']
11
+
12
+ # Drop rows with missing reviews
13
+ df = df.dropna(subset=['reviews.text', 'name'])
14
+
15
+ documents = []
16
+ for _, row in df.iterrows():
17
+ name = row['name']
18
+ brand = row.get('brand', 'Unknown')
19
+ categories = row.get('categories', 'N/A')
20
+ text = row['reviews.text']
21
+ title = row.get('reviews.title', '')
22
+ rating = row.get('reviews.rating', 'N/A')
23
+
24
+ # Parse price
25
+ price_str = "Price info not available"
26
+ prices_raw = row.get('prices')
27
+ if pd.notna(prices_raw):
28
+ try:
29
+ # The CSV might have escaped quotes
30
+ prices_data = json.loads(prices_raw.replace('""', '"'))
31
+ if isinstance(prices_data, list) and len(prices_data) > 0:
32
+ best_price = min([p.get('amountMin', float('inf')) for p in prices_data])
33
+ currency = prices_data[0].get('currency', 'USD')
34
+ if best_price != float('inf'):
35
+ price_str = f"{best_price} {currency}"
36
+ except:
37
+ pass
38
+
39
+ doc_content = f"Product: {name}\nBrand: {brand}\nCategories: {categories}\nPrice: {price_str}\nReview Title: {title}\nRating: {rating}\nReview Content: {text}"
40
+
41
+ metadata = {
42
+ "name": name,
43
+ "brand": brand,
44
+ "rating": str(rating),
45
+ "price": price_str
46
+ }
47
+
48
+ documents.append({"content": doc_content, "metadata": metadata})
49
+
50
+ return documents
51
+
52
+ if __name__ == "__main__":
53
+ docs = preprocess_data("7817_1.csv")
54
+ with open("preprocessed_docs.json", "w") as f:
55
+ json.dump(docs, f, indent=2)
56
+ print(f"Preprocessed {len(docs)} documents.")
preprocessed_docs.json ADDED
The diff for this file is too large to render. See raw diff
 
rag_model.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from dotenv import load_dotenv
4
+ from langchain_google_genai import ChatGoogleGenerativeAI
5
+ from langchain_community.vectorstores import Chroma
6
+ from langchain_community.embeddings import HuggingFaceEmbeddings
7
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
8
+ from langchain_classic.chains import RetrievalQA
9
+ from langchain_core.prompts import PromptTemplate
10
+ from langchain_core.documents import Document
11
+
12
+ load_dotenv(override=True)
13
+
14
+ # Configuration
15
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
16
+ if GOOGLE_API_KEY:
17
+ print(f"RAG Model: API Key loaded (starting with {GOOGLE_API_KEY[:5]}...)")
18
+ else:
19
+ print("RAG Model: No API Key found in environment!")
20
+ DATA_FILE = "preprocessed_docs.json"
21
+ PERSIST_DIRECTORY = "chroma_db"
22
+
23
+ def build_rag_system():
24
+ if not os.path.exists(DATA_FILE):
25
+ print("Data file not found. Please run preprocess.py first.")
26
+ return None
27
+
28
+ with open(DATA_FILE, "r") as f:
29
+ docs_data = json.load(f)
30
+
31
+ print(f"Loading {len(docs_data)} documents...")
32
+
33
+ # Prepare documents for LangChain
34
+ documents = [Document(page_content=d["content"], metadata=d["metadata"]) for d in docs_data]
35
+
36
+ # Split documents into chunks (optional, but good practice)
37
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
38
+ chunks = text_splitter.split_documents(documents)
39
+
40
+ print(f"Created {len(chunks)} chunks.")
41
+
42
+ # Initialize Embeddings
43
+ print("Initializing embeddings (HuggingFace)...")
44
+ embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
45
+
46
+ # Initialize Vector Store
47
+ print("Building vector store...")
48
+ vectorstore = Chroma.from_documents(
49
+ documents=chunks,
50
+ embedding=embeddings,
51
+ persist_directory=PERSIST_DIRECTORY
52
+ )
53
+ vectorstore.persist()
54
+ print("Vector store built and persisted.")
55
+
56
+ return vectorstore
57
+
58
+ def get_qa_chain(vectorstore):
59
+ if not GOOGLE_API_KEY:
60
+ print("Warning: GOOGLE_API_KEY not found. LLM functionality will not work.")
61
+ return None
62
+
63
+ llm = ChatGoogleGenerativeAI(model="gemini-flash-latest", google_api_key=GOOGLE_API_KEY)
64
+
65
+ # Custom Prompt
66
+ template = """You are a helpful and expert Retail Product Assistant.
67
+
68
+ Context (Product Details & Reviews):
69
+ {context}
70
+
71
+ Rules:
72
+ 1. If the user says "hi", "hello" or greets you, greet them back warmly and mention 2-3 popular products from the context to get started.
73
+ 2. Use the provided context to answer specific questions.
74
+ 3. If the answer is not in the context, politely say you don't have that specific information.
75
+ 4. Maintain a professional yet friendly tone.
76
+ 5. Always use Markdown for formatting (bolding, lists, etc.) to make it easy to read.
77
+ 6. Use bullet points if listing features or pros/cons.
78
+ 7. IMPORTANT: Convert all prices from USD to Indian Rupees (INR) using an approximate exchange rate of 1 USD = ₹83 INR. Always display the price in INR (₹).
79
+
80
+ Question: {question}
81
+ Answer:"""
82
+
83
+ QA_CHAIN_PROMPT = PromptTemplate.from_template(template)
84
+
85
+ qa_chain = RetrievalQA.from_chain_type(
86
+ llm=llm,
87
+ chain_type="stuff",
88
+ retriever=vectorstore.as_retriever(),
89
+ return_source_documents=True,
90
+ chain_type_kwargs={"prompt": QA_CHAIN_PROMPT}
91
+ )
92
+
93
+ return qa_chain
94
+
95
+ if __name__ == "__main__":
96
+ # Check if vector store exists
97
+ if os.path.exists(PERSIST_DIRECTORY):
98
+ print("Vector store already exists. Loading...")
99
+ embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
100
+ vectorstore = Chroma(persist_directory=PERSIST_DIRECTORY, embedding_function=embeddings)
101
+ else:
102
+ vectorstore = build_rag_system()
103
+
104
+ if vectorstore:
105
+ qa_chain = get_qa_chain(vectorstore)
106
+ if qa_chain:
107
+ query = "Which Kindle model has the best resolution according to reviews?"
108
+ print(f"\nQuestion: {query}")
109
+ result = qa_chain({"query": query})
110
+ print(f"\nAnswer: {result['result']}")
111
+ print("\nSources:")
112
+ for doc in result["source_documents"][:2]:
113
+ print(f"- {doc.metadata['name']} (Rating: {doc.metadata['rating']})")
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ flask
2
+ python-dotenv
3
+ langchain
4
+ langchain-community
5
+ langchain-google-genai
6
+ langchain-text-splitters
7
+ langchain-classic
8
+ chromadb
9
+ sentence-transformers
10
+ pandas
11
+ google-generativeai
12
+ huggingface_hub
13
+ gunicorn
static/css/style.css ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary-color: #6366f1;
3
+ --primary-hover: #4f46e5;
4
+ --bg-dark: #0f172a;
5
+ --bg-card: rgba(30, 41, 59, 0.7);
6
+ --text-main: #f8fafc;
7
+ --text-muted: #94a3b8;
8
+ --glass-border: rgba(255, 255, 255, 0.1);
9
+ --accent-glow: rgba(99, 102, 241, 0.2);
10
+ }
11
+
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ font-family: 'Inter', sans-serif;
17
+ }
18
+
19
+ body {
20
+ background-color: var(--bg-dark);
21
+ background-image:
22
+ radial-gradient(at 0% 0%, rgba(99, 102, 241, 0.15) 0, transparent 50%),
23
+ radial-gradient(at 100% 100%, rgba(168, 85, 247, 0.15) 0, transparent 50%);
24
+ color: var(--text-main);
25
+ min-height: 100vh;
26
+ display: flex;
27
+ flex-direction: column;
28
+ align-items: center;
29
+ padding: 2rem;
30
+ }
31
+
32
+ .container {
33
+ width: 100%;
34
+ max-width: 900px;
35
+ z-index: 1;
36
+ }
37
+
38
+ header {
39
+ text-align: center;
40
+ margin-bottom: 3rem;
41
+ animation: fadeInDown 0.8s ease-out;
42
+ }
43
+
44
+ h1 {
45
+ font-size: 2.5rem;
46
+ font-weight: 800;
47
+ background: linear-gradient(to right, #818cf8, #c084fc);
48
+ -webkit-background-clip: text;
49
+ -webkit-text-fill-color: transparent;
50
+ margin-bottom: 0.5rem;
51
+ }
52
+
53
+ p.subtitle {
54
+ color: var(--text-muted);
55
+ font-size: 1.1rem;
56
+ }
57
+
58
+ .chat-card {
59
+ background: var(--bg-card);
60
+ backdrop-filter: blur(12px);
61
+ border: 1px solid var(--glass-border);
62
+ border-radius: 1.5rem;
63
+ padding: 2rem;
64
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: 1.5rem;
68
+ animation: fadeInUp 0.8s ease-out;
69
+ }
70
+
71
+ .chat-history {
72
+ height: 400px;
73
+ overflow-y: auto;
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: 1rem;
77
+ padding-right: 0.5rem;
78
+ }
79
+
80
+ .chat-history::-webkit-scrollbar {
81
+ width: 6px;
82
+ }
83
+
84
+ .chat-history::-webkit-scrollbar-thumb {
85
+ background: var(--glass-border);
86
+ border-radius: 10px;
87
+ }
88
+
89
+ .message {
90
+ padding: 1rem;
91
+ border-radius: 1rem;
92
+ max-width: 85%;
93
+ line-height: 1.5;
94
+ }
95
+
96
+ .user-message {
97
+ background: var(--primary-color);
98
+ align-self: flex-end;
99
+ border-bottom-right-radius: 0.2rem;
100
+ }
101
+
102
+ .bot-message {
103
+ background: rgba(51, 65, 85, 0.5);
104
+ align-self: flex-start;
105
+ border-bottom-left-radius: 0.2rem;
106
+ border: 1px solid var(--glass-border);
107
+ }
108
+
109
+ .bot-message p {
110
+ margin-bottom: 0.5rem;
111
+ }
112
+
113
+ .bot-message p:last-child {
114
+ margin-bottom: 0;
115
+ }
116
+
117
+ .bot-message ul,
118
+ .bot-message ol {
119
+ margin-left: 1.5rem;
120
+ margin-bottom: 0.5rem;
121
+ }
122
+
123
+ .bot-message li {
124
+ margin-bottom: 0.25rem;
125
+ }
126
+
127
+ .input-group {
128
+ display: flex;
129
+ gap: 0.75rem;
130
+ background: rgba(0, 0, 0, 0.2);
131
+ padding: 0.5rem;
132
+ border-radius: 1rem;
133
+ border: 1px solid var(--glass-border);
134
+ }
135
+
136
+ input {
137
+ flex: 1;
138
+ background: transparent;
139
+ border: none;
140
+ color: var(--text-main);
141
+ padding: 0.75rem 1rem;
142
+ font-size: 1rem;
143
+ outline: none;
144
+ }
145
+
146
+ button {
147
+ background: var(--primary-color);
148
+ color: white;
149
+ border: none;
150
+ padding: 0.75rem 1.5rem;
151
+ border-radius: 0.75rem;
152
+ font-weight: 600;
153
+ cursor: pointer;
154
+ transition: all 0.3s ease;
155
+ }
156
+
157
+ button:hover {
158
+ background: var(--primary-hover);
159
+ transform: translateY(-2px);
160
+ box-shadow: 0 10px 15px -3px var(--accent-glow);
161
+ }
162
+
163
+ .sources {
164
+ margin-top: 0.5rem;
165
+ font-size: 0.8rem;
166
+ color: var(--text-muted);
167
+ border-top: 1px solid var(--glass-border);
168
+ padding-top: 0.5rem;
169
+ }
170
+
171
+ .source-tag {
172
+ display: inline-block;
173
+ background: rgba(99, 102, 241, 0.15);
174
+ padding: 0.2rem 0.6rem;
175
+ border-radius: 0.4rem;
176
+ margin-right: 0.5rem;
177
+ margin-top: 0.5rem;
178
+ border: 1px solid rgba(99, 102, 241, 0.2);
179
+ color: #a5b4fc;
180
+ }
181
+
182
+ .suggestions {
183
+ margin: 0.5rem 0;
184
+ }
185
+
186
+ .suggestions p {
187
+ font-size: 0.85rem;
188
+ color: var(--text-muted);
189
+ margin-bottom: 0.75rem;
190
+ }
191
+
192
+ .suggestion-chips {
193
+ display: flex;
194
+ flex-wrap: wrap;
195
+ gap: 0.5rem;
196
+ }
197
+
198
+ .chip {
199
+ background: rgba(255, 255, 255, 0.05);
200
+ border: 1px solid var(--glass-border);
201
+ color: var(--text-main);
202
+ padding: 0.4rem 0.8rem;
203
+ border-radius: 2rem;
204
+ font-size: 0.8rem;
205
+ cursor: pointer;
206
+ transition: all 0.2s ease;
207
+ width: auto;
208
+ }
209
+
210
+ .chip:hover {
211
+ background: rgba(99, 102, 241, 0.2);
212
+ border-color: var(--primary-color);
213
+ transform: translateY(-1px);
214
+ }
215
+
216
+ @keyframes fadeInDown {
217
+ from {
218
+ opacity: 0;
219
+ transform: translateY(-20px);
220
+ }
221
+
222
+ to {
223
+ opacity: 1;
224
+ transform: translateY(0);
225
+ }
226
+ }
227
+
228
+ @keyframes fadeInUp {
229
+ from {
230
+ opacity: 0;
231
+ transform: translateY(20px);
232
+ }
233
+
234
+ to {
235
+ opacity: 1;
236
+ transform: translateY(0);
237
+ }
238
+ }
239
+
240
+ .loading-dots:after {
241
+ content: '...';
242
+ display: inline-block;
243
+ width: 0;
244
+ animation: dots 1.5s steps(4, end) infinite;
245
+ overflow: hidden;
246
+ vertical-align: bottom;
247
+ }
248
+
249
+ @keyframes dots {
250
+ to {
251
+ width: 1.25em;
252
+ }
253
+ }
static/js/main.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const chatHistory = document.getElementById('chat-history');
3
+ const queryInput = document.getElementById('query-input');
4
+ const sendBtn = document.getElementById('send-btn');
5
+
6
+ const appendMessage = (content, isUser, sources = []) => {
7
+ const msgDiv = document.createElement('div');
8
+ msgDiv.className = `message ${isUser ? 'user-message' : 'bot-message'}`;
9
+
10
+ // Parse markdown if it's from the bot
11
+ const parsedContent = isUser ? content : marked.parse(content);
12
+ let html = `<div class="content">${parsedContent}</div>`;
13
+
14
+ if (!isUser && sources.length > 0) {
15
+ html += '<div class="sources"><strong>Sources:</strong><br>';
16
+ sources.forEach(src => {
17
+ html += `<span class="source-tag">${src}</span>`;
18
+ });
19
+ html += '</div>';
20
+ }
21
+
22
+ msgDiv.innerHTML = html;
23
+ chatHistory.appendChild(msgDiv);
24
+ chatHistory.scrollTop = chatHistory.scrollHeight;
25
+ };
26
+
27
+ const handleQuery = async (forcedQuery = null) => {
28
+ const query = (forcedQuery || queryInput.value).trim();
29
+ if (!query) return;
30
+
31
+ appendMessage(query, true);
32
+ queryInput.value = '';
33
+
34
+ // Add loading indicator
35
+ const loadingDiv = document.createElement('div');
36
+ loadingDiv.className = 'message bot-message loading-indicator';
37
+ loadingDiv.innerHTML = '<span class="loading-dots">Thinking</span>';
38
+ chatHistory.appendChild(loadingDiv);
39
+ chatHistory.scrollTop = chatHistory.scrollHeight;
40
+
41
+ try {
42
+ const response = await fetch('/ask', {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': 'application/json' },
45
+ body: JSON.stringify({ query })
46
+ });
47
+
48
+ const data = await response.json();
49
+
50
+ // Remove loading
51
+ chatHistory.removeChild(loadingDiv);
52
+
53
+ if (data.answer) {
54
+ appendMessage(data.answer, false, data.sources);
55
+ } else {
56
+ appendMessage("Sorry, I encountered an error: " + (data.error || "Unknown error"), false);
57
+ }
58
+ } catch (error) {
59
+ chatHistory.removeChild(loadingDiv);
60
+ appendMessage("Connection error. Is the server running?", false);
61
+ }
62
+ };
63
+
64
+ // Suggestion chips
65
+ document.querySelectorAll('.chip').forEach(chip => {
66
+ chip.addEventListener('click', () => {
67
+ handleQuery(chip.getAttribute('data-query'));
68
+ });
69
+ });
70
+
71
+ sendBtn.addEventListener('click', () => handleQuery());
72
+ queryInput.addEventListener('keypress', (e) => {
73
+ if (e.key === 'Enter') handleQuery();
74
+ });
75
+ });
templates/index.html ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Retail Assistant | Product Knowledge RAG</title>
8
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
13
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
14
+ </head>
15
+
16
+ <body>
17
+ <div class="container">
18
+ <header>
19
+ <h1><i class="fas fa-robot"></i> Retail Genius</h1>
20
+ <p class="subtitle">AI-Powered Product Knowledge Assistant</p>
21
+ </header>
22
+
23
+ <main class="chat-card">
24
+ <div id="chat-history" class="chat-history">
25
+ <div class="message bot-message">
26
+ <i class="fas fa-hand-wave"></i> Hello! I'm your retail knowledge assistant. Ask me anything about
27
+ our products, pricing, or customer reviews!
28
+ </div>
29
+ </div>
30
+
31
+ <div class="suggestions">
32
+ <p><i class="fas fa-lightbulb"></i> Try asking:</p>
33
+ <div class="suggestion-chips">
34
+ <button class="chip" data-query="Which Kindle has the best resolution?">Kindle Resolution</button>
35
+ <button class="chip" data-query="Compare Paperwhite vs Voyage battery life.">Paperwhite vs
36
+ Voyage</button>
37
+ <button class="chip" data-query="What do reviews say about the Fire TV?">Fire TV Reviews</button>
38
+ <button class="chip" data-query="Is there a Kindle with physical buttons?">Physical
39
+ Buttons?</button>
40
+ </div>
41
+ </div>
42
+
43
+ <div class="input-group">
44
+ <input type="text" id="query-input" placeholder="Ask a question..." autocomplete="off">
45
+ <button id="send-btn"><i class="fas fa-paper-plane"></i></button>
46
+ </div>
47
+ </main>
48
+ </div>
49
+ <script src="{{ url_for('static', filename='js/main.js') }}"></script>
50
+ </body>
51
+
52
+ </html>