logan-codes commited on
Commit
5fc13d5
·
1 Parent(s): bac1636

ui improvements with glassmorphic effect

Browse files
app/services/document_ingester.py CHANGED
@@ -15,7 +15,7 @@ class Ingester:
15
  )
16
  load_dotenv()
17
  base_dir = os.getenv("HF_HOME", "/home/user/app")
18
- self.DATA_DIR = os.path.join(base_dir, "data")
19
  self.vector_store= Chroma(
20
  collection_name="documents_collection",
21
  embedding_function=self.embedding_model,
 
15
  )
16
  load_dotenv()
17
  base_dir = os.getenv("HF_HOME", "/home/user/app")
18
+ self.DATA_DIRaw = os.path.join(base_dir, "data")
19
  self.vector_store= Chroma(
20
  collection_name="documents_collection",
21
  embedding_function=self.embedding_model,
ui/.streamlit/config.toml DELETED
@@ -1,3 +0,0 @@
1
- [server]
2
- enableXsrfProtection = false
3
- enableCORS = false
 
 
 
 
ui/Home.py CHANGED
@@ -1,4 +1,5 @@
1
  import streamlit as st
 
2
 
3
  st.set_page_config(
4
  page_title="Knowledge Management RAG",
@@ -6,80 +7,166 @@ st.set_page_config(
6
  layout="wide"
7
  )
8
 
 
 
9
  # Hero Section
10
- st.title("🧠 Knowledge Management System")
11
- st.markdown("""
12
- ### Your Personal AI-Powered Knowledge Base
 
 
13
 
14
- Welcome to a local-first Retrieval-Augmented Generation (RAG) system designed to help you
15
- **organize**, **search**, and **chat** with your documents. Powered by advanced AI models,
16
- this tool transforms your static files into an interactive knowledge engine.
17
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  st.divider()
20
 
21
  # Key Features Section
22
- st.header("✨ Key Features")
23
 
24
  col1, col2, col3 = st.columns(3)
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  with col1:
27
- st.subheader("📄 Smart Ingestion")
28
- st.markdown("""
29
- - **Advanced Parsing**: Uses [Docling](https://github.com/DS4SD/docling) for high-fidelity PDF & document processing.
30
- - **Async Processing**: Upload large files without blocking the UI.
31
- - **State Management**: Track document status from upload to full indexing.
32
- """)
 
 
 
 
33
 
34
  with col2:
35
- st.subheader("🤖 Intelligent Retrieval")
36
- st.markdown("""
37
- - **Semantic Search**: Powered by `all-MiniLM-L6-v2` embeddings.
38
- - **Vector Database**: Fast and scalable storage using ChromaDB (local) or Qdrant.
39
- - **Query Expansion**: Generates multiple perspectives for better recall.
40
- """)
 
 
 
 
41
 
42
  with col3:
43
- st.subheader("💬 Context-Aware Chat")
44
- st.markdown("""
45
- - **Gemini Powered**: Uses Google's Gemini 2.5 Flash Lite for accurate reasoning.
46
- - **History Aware**: Remembers conversation context for fluid interaction.
47
- - **Source Citations**: Know exactly where the answer came from (Coming Soon).
48
- """)
 
 
 
 
49
 
50
  st.divider()
51
 
 
52
  # How It Works / Getting Started
53
- st.header("🚀 Getting Started")
54
 
55
  step1, step2, step3 = st.columns(3)
56
 
57
  with step1:
58
- st.markdown("#### 1. Upload Documents")
59
- st.info("Go to the **Documents** page and upload your PDFs, DOCX, or TXT files.")
 
 
 
 
 
 
60
 
61
  with step2:
62
- st.markdown("#### 2. Process & Index")
63
- st.warning("The system automatically processes files in the background. Watch the status change to 'Ingested'.")
 
 
 
 
 
64
 
65
  with step3:
66
- st.markdown("#### 3. Chat with Data")
67
- st.success("Switch to the **Chat** page and ask questions about your knowledge base.")
 
 
 
 
 
68
 
69
  st.divider()
70
 
71
  # Tech Stack Footer
72
  with st.expander("🛠️ Under the Hood"):
73
  st.markdown("""
74
- This project is built with a modern, robust tech stack:
75
- - **Backend**: FastAPI (Python)
76
- - **Frontend**: Streamlit
77
- - **LLM**: Google Gemini 2.5 Flash Lite
78
- - **Embeddings**: HuggingFace (`sentence-transformers`)
79
- - **Vector Store**: ChromaDB (local)
80
- - **State Management**: SQLite
81
- - **Parsing**: Docling
82
- """)
83
-
84
- st.markdown("---")
85
- st.caption("Built with ❤️ by Logan | version 2.0.0")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ from utils import load_css
3
 
4
  st.set_page_config(
5
  page_title="Knowledge Management RAG",
 
7
  layout="wide"
8
  )
9
 
10
+ load_css()
11
+
12
  # Hero Section
13
+ col1, col2 = st.columns([2, 1])
14
+
15
+ with col1:
16
+ st.markdown('<h1 class="gradient-text" style="font-size: 3rem; margin-bottom: 10px;">Knowledge Management</h1>', unsafe_allow_html=True)
17
+ st.markdown('<h3 style="color: #b0b0b0; margin-top: 0;">Your Second Brain, Supercharged by AI</h3>', unsafe_allow_html=True)
18
 
19
+ st.markdown("""
20
+ <div style="padding: 24px; background: rgba(67, 97, 238, 0.08); border-radius: 16px; border-left: 6px solid #4361EE; margin-top: 20px;">
21
+ <p style="font-size: 1.15rem; margin: 0; line-height: 1.6; color: #e0e0e0;">
22
+ Welcome to your local-first <strong>RAG System</strong>.
23
+ Seamlessly upload documents, index them with advanced semantic search, and chat with your data using
24
+ state-of-the-art AI models. Secure, private, and powerful.
25
+ </p>
26
+ </div>
27
+ """, unsafe_allow_html=True)
28
+
29
+ with col2:
30
+ # Statistic / Quick Action Placeholder
31
+ st.markdown("""
32
+ <div class="custom-card" style="text-align: center; display: flex; flex-direction: column; justify-content: center; height: 100%; border: 1px solid rgba(67, 97, 238, 0.3); background: rgba(67, 97, 238, 0.05);">
33
+ <div style="font-size: 3.5rem; margin-bottom: 10px; filter: drop-shadow(0 0 20px rgba(67, 97, 238, 0.4));">🚀</div>
34
+ <h3 style="margin-top: 5px; font-weight: 800; color: #fff;">Get Started</h3>
35
+ <p style="color: #bbb; margin-bottom: 15px; font-size: 0.9rem;">Upload your first document to unlock the power of AI.</p>
36
+ </div>
37
+ """, unsafe_allow_html=True)
38
+ st.page_link("pages/Documents.py", label="Start Uploading", icon="📂", use_container_width=True)
39
 
40
  st.divider()
41
 
42
  # Key Features Section
43
+ st.markdown('<h2 style="text-align: center; margin-bottom: 40px;">Powerful Features</h2>', unsafe_allow_html=True)
44
 
45
  col1, col2, col3 = st.columns(3)
46
 
47
+ def feature_card(icon, title, points, color_accent):
48
+ points_html = "".join([f"<li style='margin-bottom: 8px;'>{p}</li>" for p in points])
49
+ return f"""
50
+ <div class="custom-card" style="border-top: 4px solid {color_accent};">
51
+ <div style="font-size: 2.5rem; margin-bottom: 15px;">{icon}</div>
52
+ <div class="card-title" style="background: {color_accent}; -webkit-background-clip: text; -webkit-text-fill-color: transparent;">{title}</div>
53
+ <div class="card-text">
54
+ <ul style="padding-left: 20px; margin-top: 0;">
55
+ {points_html}
56
+ </ul>
57
+ </div>
58
+ </div>
59
+ """
60
+
61
  with col1:
62
+ st.markdown(feature_card(
63
+ "📄",
64
+ "Smart Ingestion",
65
+ [
66
+ "<strong>Docling Parsing:</strong> High-fidelity PDF processing.",
67
+ "<strong>Async Processing:</strong> Non-blocking uploads.",
68
+ "<strong>Status Tracking:</strong> Real-time updates."
69
+ ],
70
+ "linear-gradient(90deg, #4CC9F0, #4361EE)" # Blue Cyan Gradient
71
+ ), unsafe_allow_html=True)
72
 
73
  with col2:
74
+ st.markdown(feature_card(
75
+ "🧠",
76
+ "Intelligent Retrieval",
77
+ [
78
+ "<strong>Semantic Search:</strong> Deep meaning extraction.",
79
+ "<strong>Local Vector DB:</strong> Private ChromaDB storage.",
80
+ "<strong>Query Expansion:</strong> Enhanced recall."
81
+ ],
82
+ "linear-gradient(90deg, #7209B7, #F72585)" # Purple Pink Gradient
83
+ ), unsafe_allow_html=True)
84
 
85
  with col3:
86
+ st.markdown(feature_card(
87
+ "💬",
88
+ "Context-Aware Chat",
89
+ [
90
+ "<strong>Gemini Powered:</strong> Advanced reasoning.",
91
+ "<strong>History Aware:</strong> Fluid conversations.",
92
+ "<strong>Citations:</strong> (Coming Soon) Fact-checking."
93
+ ],
94
+ "linear-gradient(90deg, #FF9E00, #FF0054)" # Orange Red Gradient
95
+ ), unsafe_allow_html=True)
96
 
97
  st.divider()
98
 
99
+
100
  # How It Works / Getting Started
101
+ st.markdown('<h2 style="margin-bottom: 30px;">🚀 How It Works</h2>', unsafe_allow_html=True)
102
 
103
  step1, step2, step3 = st.columns(3)
104
 
105
  with step1:
106
+ st.markdown("""
107
+ <div class="custom-card" style="text-align: center; border: 1px solid rgba(76, 201, 240, 0.2);">
108
+ <div style="font-size: 2rem; color: #4CC9F0; font-weight: 800; margin-bottom: 10px;">01</div>
109
+ <h4>Upload</h4>
110
+ <p style="color: #aaa; margin-bottom: 15px;">Go to <strong>Documents</strong> and upload your files.</p>
111
+ </div>
112
+ """, unsafe_allow_html=True)
113
+ st.page_link("pages/Documents.py", label="Go to Documents", icon="📂", use_container_width=True)
114
 
115
  with step2:
116
+ st.markdown("""
117
+ <div class="custom-card" style="text-align: center; border: 1px solid rgba(114, 9, 183, 0.2);">
118
+ <div style="font-size: 2rem; color: #7209B7; font-weight: 800; margin-bottom: 10px;">02</div>
119
+ <h4>Process</h4>
120
+ <p style="color: #aaa;">The system parses, embeds, and indexes your content automatically.</p>
121
+ </div>
122
+ """, unsafe_allow_html=True)
123
 
124
  with step3:
125
+ st.markdown("""
126
+ <div class="custom-card" style="text-align: center; border: 1px solid rgba(247, 37, 133, 0.2);">
127
+ <div style="font-size: 2rem; color: #F72585; font-weight: 800; margin-bottom: 10px;">03</div>
128
+ <h4>Chat</h4>
129
+ <p style="color: #aaa;">Ask anything. Get instant answers grounded in your data.</p>
130
+ </div>
131
+ """, unsafe_allow_html=True)
132
 
133
  st.divider()
134
 
135
  # Tech Stack Footer
136
  with st.expander("🛠️ Under the Hood"):
137
  st.markdown("""
138
+ <ul style="list-style-type: none; padding: 0; margin: 0;">
139
+ <li style="margin-bottom: 10px; padding: 10px; background: rgba(255,255,255,0.05); border-radius: 8px; display: flex; align-items: center;">
140
+ <span style="font-size: 1.2rem; margin-right: 15px;">🐍</span>
141
+ <div>
142
+ <strong style="color: #4361EE;">Backend:</strong> FastAPI (Python)
143
+ </div>
144
+ </li>
145
+ <li style="margin-bottom: 10px; padding: 10px; background: rgba(255,255,255,0.05); border-radius: 8px; display: flex; align-items: center;">
146
+ <span style="font-size: 1.2rem; margin-right: 15px;">🎨</span>
147
+ <div>
148
+ <strong style="color: #F72585;">Frontend:</strong> Streamlit
149
+ </div>
150
+ </li>
151
+ <li style="margin-bottom: 10px; padding: 10px; background: rgba(255,255,255,0.05); border-radius: 8px; display: flex; align-items: center;">
152
+ <span style="font-size: 1.2rem; margin-right: 15px;">🧠</span>
153
+ <div>
154
+ <strong style="color: #4CC9F0;">AI Model:</strong> Google Gemini 2.5 Flash
155
+ </div>
156
+ </li>
157
+ <li style="margin-bottom: 10px; padding: 10px; background: rgba(255,255,255,0.05); border-radius: 8px; display: flex; align-items: center;">
158
+ <span style="font-size: 1.2rem; margin-right: 15px;">🗄️</span>
159
+ <div>
160
+ <strong style="color: #7209B7;">Vector DB:</strong> ChromaDB (Local)
161
+ </div>
162
+ </li>
163
+ <li style="margin-bottom: 10px; padding: 10px; background: rgba(255,255,255,0.05); border-radius: 8px; display: flex; align-items: center;">
164
+ <span style="font-size: 1.2rem; margin-right: 15px;">📄</span>
165
+ <div>
166
+ <strong style="color: #FF9E00;">Parser:</strong> Docling
167
+ </div>
168
+ </li>
169
+ </ul>
170
+ """, unsafe_allow_html=True)
171
+
172
+ st.markdown("<div style='text-align: center; margin-top: 50px; color: #666;'>Built with ❤️ by Logan | version 2.1.0</div>", unsafe_allow_html=True)
ui/pages/Chat.py CHANGED
@@ -2,31 +2,40 @@ import streamlit as st
2
  import requests
3
  import os
4
  import json
 
5
  from dotenv import load_dotenv
 
 
 
 
 
6
  load_dotenv()
7
 
8
  API_URL = os.getenv("API_URL", "http://localhost:8000/")
9
 
 
 
10
 
11
- st.set_page_config(page_title="Chat App", page_icon="💬")
12
  col_title, col_btn = st.columns([0.8, 0.2])
13
  with col_title:
14
- st.title("💬 Chat Interface")
15
  with col_btn:
16
- if st.button("Clear Chat History"):
17
  st.session_state.messages = []
18
  st.rerun()
19
 
20
  if "messages" not in st.session_state:
21
  st.session_state.messages = []
22
 
 
 
23
 
24
- for message in st.session_state.messages:
25
- with st.chat_message(message["role"]):
26
- st.markdown(message["content"])
27
-
28
 
29
- if prompt := st.chat_input("Type your message..."):
30
  st.session_state.messages.append(
31
  {"role": "user", "content": prompt}
32
  )
 
2
  import requests
3
  import os
4
  import json
5
+ import sys
6
  from dotenv import load_dotenv
7
+
8
+ # Add parent directory to path to import utils
9
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
10
+ from utils import load_css
11
+
12
  load_dotenv()
13
 
14
  API_URL = os.getenv("API_URL", "http://localhost:8000/")
15
 
16
+ st.set_page_config(page_title="Chat App", page_icon="💬", layout="wide")
17
+ load_css()
18
 
 
19
  col_title, col_btn = st.columns([0.8, 0.2])
20
  with col_title:
21
+ st.markdown('<h1 class="gradient-text">💬 Chat Interface</h1>', unsafe_allow_html=True)
22
  with col_btn:
23
+ if st.button("Clear History", type="primary"):
24
  st.session_state.messages = []
25
  st.rerun()
26
 
27
  if "messages" not in st.session_state:
28
  st.session_state.messages = []
29
 
30
+ # Chat container
31
+ chat_container = st.container()
32
 
33
+ with chat_container:
34
+ for message in st.session_state.messages:
35
+ with st.chat_message(message["role"]):
36
+ st.markdown(message["content"])
37
 
38
+ if prompt := st.chat_input("Ask a question about your documents..."):
39
  st.session_state.messages.append(
40
  {"role": "user", "content": prompt}
41
  )
ui/pages/Documents.py CHANGED
@@ -1,15 +1,21 @@
1
  import streamlit as st
2
  import requests
3
  import os
4
- import pandas as pd
5
  from dotenv import load_dotenv
6
 
 
 
 
 
7
  load_dotenv()
8
 
9
  API_URL = os.getenv("API_URL", "http://localhost:8000/")
10
 
11
- st.set_page_config(page_title="Documents", page_icon="📄")
12
- st.title("📄 Document Management")
 
 
13
 
14
  def fetch_documents():
15
  try:
@@ -37,15 +43,22 @@ def delete_document(name):
37
  return None
38
  return res
39
 
 
40
  st.subheader("📤 Upload Document")
 
 
 
 
 
41
 
42
  uploaded_file = st.file_uploader(
43
  "Choose a file",
44
- type=["pdf", "docx", "txt"]
 
45
  )
46
 
47
  if uploaded_file:
48
- if st.button("Upload and Ingest"):
49
  with st.spinner("Uploading and ingesting document..."):
50
  res = upload_document(uploaded_file)
51
 
@@ -58,12 +71,16 @@ if uploaded_file:
58
 
59
  st.divider()
60
 
 
61
  st.subheader("📄 Available Documents")
62
 
63
- search_query = st.text_input(
64
- "",
65
- placeholder="🔍 Search documents"
66
- )
 
 
 
67
 
68
  with st.spinner("Fetching documents..."):
69
  documents = fetch_documents()
@@ -73,41 +90,45 @@ if search_query:
73
  doc for doc in documents
74
  if search_query.lower() in doc[0].lower()
75
  ]
 
76
  if not documents:
77
  st.info("No documents available.")
78
  else:
79
- # Table header
80
- header_cols = st.columns([3, 2, 2, 1])
81
- header_cols[0].markdown("**Filename**")
82
- header_cols[1].markdown("**Status**")
83
- header_cols[2].markdown("**Uploaded At**")
84
- header_cols[3].markdown("**Actions**")
85
-
86
- st.divider()
 
87
 
88
  for idx, doc in enumerate(documents):
89
  filename, status, timestamp, path = doc
90
-
91
- cols = st.columns([3, 2, 2, 1])
92
- status_color = "green" if status == "ingested" else "orange"
93
  status_display = f"✅ {status.capitalize()}" if status == "ingested" else f"⏳ {status.capitalize()}"
94
- status_markdown = f"<span style='color:{status_color}'>{status_display}</span>"
95
- cols[0].write(filename)
96
- cols[1].markdown(status_markdown, unsafe_allow_html=True)
97
- cols[2].write(timestamp)
98
-
99
- if cols[3].button(
100
- "Delete",
101
- key=f"delete_{idx}",
102
- type="secondary"
103
- ):
104
- with st.spinner("Deleting document..."):
105
- res = delete_document(path)
106
-
107
- if res is None:
108
- pass
109
- elif res.status_code == 200:
110
- st.success(f"✅ Deleted `{filename}`")
111
- st.rerun()
112
- else:
113
- st.error("❌ Failed to delete document")
 
 
 
 
1
  import streamlit as st
2
  import requests
3
  import os
4
+ import sys
5
  from dotenv import load_dotenv
6
 
7
+ # Add parent directory to path to import utils
8
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
9
+ from utils import load_css
10
+
11
  load_dotenv()
12
 
13
  API_URL = os.getenv("API_URL", "http://localhost:8000/")
14
 
15
+ st.set_page_config(page_title="Documents", page_icon="📄", layout="wide")
16
+ load_css()
17
+
18
+ st.markdown('<h1 class="gradient-text">📄 Document Management</h1>', unsafe_allow_html=True)
19
 
20
  def fetch_documents():
21
  try:
 
43
  return None
44
  return res
45
 
46
+ # Upload Section
47
  st.subheader("📤 Upload Document")
48
+ st.markdown("""
49
+ <div style="background: rgba(255, 255, 255, 0.05); padding: 20px; border-radius: 12px; border: 1px dashed rgba(255, 255, 255, 0.2); margin-bottom: 20px;">
50
+ <p style="margin: 0; color: #aaa;">Supported formats: PDF, DOCX, TXT</p>
51
+ </div>
52
+ """, unsafe_allow_html=True)
53
 
54
  uploaded_file = st.file_uploader(
55
  "Choose a file",
56
+ type=["pdf", "docx", "txt"],
57
+ label_visibility="collapsed"
58
  )
59
 
60
  if uploaded_file:
61
+ if st.button("Upload and Ingest", type="primary"):
62
  with st.spinner("Uploading and ingesting document..."):
63
  res = upload_document(uploaded_file)
64
 
 
71
 
72
  st.divider()
73
 
74
+ # Documents List
75
  st.subheader("📄 Available Documents")
76
 
77
+ search_col, _ = st.columns([1, 1])
78
+ with search_col:
79
+ search_query = st.text_input(
80
+ "",
81
+ placeholder="🔍 Search documents...",
82
+ label_visibility="collapsed"
83
+ )
84
 
85
  with st.spinner("Fetching documents..."):
86
  documents = fetch_documents()
 
90
  doc for doc in documents
91
  if search_query.lower() in doc[0].lower()
92
  ]
93
+
94
  if not documents:
95
  st.info("No documents available.")
96
  else:
97
+ # Header
98
+ st.markdown("""
99
+ <div style="display: flex; font-weight: bold; color: #e0e0e0; padding: 10px 0; border-bottom: 2px solid rgba(67, 97, 238, 0.5); background: rgba(0,0,0,0.2); margin-bottom: 10px;">
100
+ <div style="flex: 3; padding-left: 10px;">Filename</div>
101
+ <div style="flex: 2;">Status</div>
102
+ <div style="flex: 2;">Uploaded At</div>
103
+ <div style="flex: 1; text-align: right; padding-right: 10px;">Actions</div>
104
+ </div>
105
+ """, unsafe_allow_html=True)
106
 
107
  for idx, doc in enumerate(documents):
108
  filename, status, timestamp, path = doc
109
+
110
+ status_class = "status-ingested" if status == "ingested" else "status-pending"
 
111
  status_display = f"✅ {status.capitalize()}" if status == "ingested" else f"⏳ {status.capitalize()}"
112
+
113
+ col1, col2, col3, col4 = st.columns([3, 2, 2, 1])
114
+
115
+ with col1:
116
+ st.markdown(f"<div style='padding-top: 5px; padding-left: 10px;'>{filename}</div>", unsafe_allow_html=True)
117
+
118
+ with col2:
119
+ st.markdown(f"<span class='status-badge {status_class}'>{status_display}</span>", unsafe_allow_html=True)
120
+
121
+ with col3:
122
+ st.markdown(f"<div style='padding-top: 5px; color: #aaa;'>{timestamp}</div>", unsafe_allow_html=True)
123
+
124
+ with col4:
125
+ if st.button("🗑️", key=f"delete_{idx}", help="Delete Document"):
126
+ with st.spinner("Deleting..."):
127
+ res = delete_document(path)
128
+ if res and res.status_code == 200:
129
+ st.success(f"Deleted {filename}")
130
+ st.rerun()
131
+ else:
132
+ st.error("Failed to delete")
133
+
134
+ st.markdown("<hr style='margin: 5px 0; border-color: rgba(255,255,255,0.05);'>", unsafe_allow_html=True)
ui/style.css ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Global Styles */
2
+ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap');
3
+
4
+ html, body, [class*="css"] {
5
+ font-family: 'Outfit', sans-serif;
6
+ color: #e0e0e0;
7
+ }
8
+
9
+ /* Glassmorphism Background with subtle animation */
10
+ .stApp {
11
+ background: radial-gradient(circle at 10% 20%, rgb(18, 22, 28) 0%, rgb(10, 10, 14) 90.2%);
12
+ background-attachment: fixed;
13
+ }
14
+
15
+ /* Custom Card - Enhanced */
16
+ .custom-card {
17
+ background: rgba(255, 255, 255, 0.03);
18
+ backdrop-filter: blur(16px);
19
+ -webkit-backdrop-filter: blur(16px);
20
+ border-radius: 16px;
21
+ padding: 24px;
22
+ border: 1px solid rgba(255, 255, 255, 0.08);
23
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3);
24
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
25
+ margin-bottom: 24px;
26
+ height: 100%;
27
+ position: relative;
28
+ overflow: hidden;
29
+ }
30
+
31
+ .custom-card::before {
32
+ content: '';
33
+ position: absolute;
34
+ top: 0;
35
+ left: 0;
36
+ width: 100%;
37
+ height: 100%;
38
+ background: linear-gradient(135deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0) 100%);
39
+ opacity: 0;
40
+ transition: opacity 0.3s ease;
41
+ }
42
+
43
+ .custom-card:hover {
44
+ transform: translateY(-8px);
45
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
46
+ border-color: rgba(255, 255, 255, 0.15);
47
+ }
48
+
49
+ .custom-card:hover::before {
50
+ opacity: 1;
51
+ }
52
+
53
+ .card-title {
54
+ font-size: 1.25rem;
55
+ font-weight: 700;
56
+ margin-bottom: 12px;
57
+ letter-spacing: -0.5px;
58
+ background: linear-gradient(90deg, #fff, #bbb);
59
+ -webkit-background-clip: text;
60
+ -webkit-text-fill-color: transparent;
61
+ }
62
+
63
+ .card-text {
64
+ color: #b0b0b0;
65
+ font-size: 0.95rem;
66
+ line-height: 1.6;
67
+ }
68
+
69
+ /* Gradient text utility */
70
+ .gradient-text {
71
+ background: linear-gradient(90deg, #4CC9F0 0%, #4361EE 100%);
72
+ -webkit-background-clip: text;
73
+ -webkit-text-fill-color: transparent;
74
+ font-weight: 800;
75
+ }
76
+
77
+ /* Buttons - Modern Gradient */
78
+ div.stButton > button {
79
+ background: linear-gradient(135deg, #4361EE 0%, #3F37C9 100%);
80
+ color: white;
81
+ border: none;
82
+ border-radius: 10px;
83
+ padding: 0.6rem 1.2rem;
84
+ font-weight: 600;
85
+ letter-spacing: 0.5px;
86
+ transition: all 0.3s ease;
87
+ box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
88
+ }
89
+
90
+ div.stButton > button[kind="secondary"] {
91
+ background: rgba(255,255,255,0.05);
92
+ border: 1px solid rgba(255,255,255,0.1);
93
+ box-shadow: none;
94
+ }
95
+
96
+ div.stButton > button:hover {
97
+ background: linear-gradient(135deg, #4895EF 0%, #4361EE 100%);
98
+ box-shadow: 0 6px 20px rgba(67, 97, 238, 0.5);
99
+ transform: translateY(-2px);
100
+ color: white;
101
+ }
102
+
103
+ div.stButton > button:active {
104
+ transform: scale(0.98);
105
+ }
106
+
107
+ /* Inputs - Sleek Dark */
108
+ .stTextInput > div > div > input {
109
+ background-color: rgba(0, 0, 0, 0.2);
110
+ color: #e0e0e0;
111
+ border: 1px solid rgba(255, 255, 255, 0.1);
112
+ border-radius: 10px;
113
+ padding: 10px 12px;
114
+ }
115
+
116
+ .stTextInput > div > div > input:focus {
117
+ border-color: #4361EE;
118
+ box-shadow: 0 0 0 2px rgba(67, 97, 238, 0.2);
119
+ }
120
+
121
+ /* Chat Messages */
122
+ .stChatMessage {
123
+ background-color: transparent;
124
+ }
125
+
126
+ .stChatMessage[data-testid="stChatMessageAvatarUser"] {
127
+ background-color: #2b2d42; /* Dark user avatar bg */
128
+ border: 1px solid rgba(255,255,255,0.1);
129
+ }
130
+
131
+ .stChatMessage[data-testid="stChatMessageAvatarAssistant"] {
132
+ background: linear-gradient(135deg, #4361EE, #F72585); /* Vibrant gradient */
133
+ }
134
+
135
+ div[data-testid="stChatMessageContent"] {
136
+ background: rgba(255, 255, 255, 0.04);
137
+ border-radius: 16px;
138
+ padding: 20px;
139
+ border: 1px solid rgba(255, 255, 255, 0.05);
140
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
141
+ }
142
+
143
+ /* Headings */
144
+ h1, h2, h3 {
145
+ color: #ffffff !important;
146
+ font-weight: 700 !important;
147
+ letter-spacing: -0.5px;
148
+ }
149
+
150
+ /* Sidebar */
151
+ section[data-testid="stSidebar"] {
152
+ background: rgba(18, 22, 28, 0.95);
153
+ border-right: 1px solid rgba(255, 255, 255, 0.1);
154
+ }
155
+
156
+ section[data-testid="stSidebar"] [data-testid="stVerticalBlock"] {
157
+ gap: 0.5rem;
158
+ }
159
+
160
+ .st-emotion-cache-16txtl3 { /* Sidebar nav links container - this class might change, targeting via attribute if possible */
161
+ padding-top: 2rem;
162
+ }
163
+
164
+ /* Sidebar Nav Links */
165
+ .stPageLink a {
166
+ background: rgba(255, 255, 255, 0.05);
167
+ border: 1px solid rgba(255, 255, 255, 0.05);
168
+ border-radius: 8px;
169
+ margin-bottom: 5px;
170
+ transition: all 0.2s ease;
171
+ }
172
+
173
+ .stPageLink a:hover {
174
+ background: rgba(67, 97, 238, 0.2);
175
+ border-color: rgba(67, 97, 238, 0.5);
176
+ transform: translateX(5px);
177
+ }
178
+
179
+ .stPageLink p {
180
+ font-weight: 600;
181
+ }
182
+
183
+ /* Divider with Gradient */
184
+ hr {
185
+ border: 0;
186
+ height: 1px;
187
+ background-image: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));
188
+ margin: 30px 0;
189
+ }
190
+
191
+ /* Status Badges - Pill shape */
192
+ .status-badge {
193
+ padding: 6px 12px;
194
+ border-radius: 20px;
195
+ font-size: 0.8rem;
196
+ font-weight: 600;
197
+ letter-spacing: 0.5px;
198
+ text-transform: uppercase;
199
+ display: inline-block;
200
+ }
201
+
202
+ .status-ingested {
203
+ background: rgba(4, 212, 137, 0.15); /* Vivid green tint */
204
+ color: #2ecc71;
205
+ border: 1px solid rgba(4, 212, 137, 0.3);
206
+ }
207
+
208
+ .status-pending {
209
+ background: rgba(255, 179, 11, 0.15); /* Amber tint */
210
+ color: #f1c40f;
211
+ border: 1px solid rgba(255, 179, 11, 0.3);
212
+ }
213
+
214
+ /* Scrollbar */
215
+ ::-webkit-scrollbar {
216
+ width: 8px;
217
+ height: 8px;
218
+ }
219
+ ::-webkit-scrollbar-track {
220
+ background: #121212;
221
+ }
222
+ ::-webkit-scrollbar-thumb {
223
+ background: #444;
224
+ border-radius: 4px;
225
+ }
226
+ ::-webkit-scrollbar-thumb:hover {
227
+ background: #666;
228
+ }
ui/utils.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+
4
+ def load_css(file_name="style.css"):
5
+ """
6
+ Loads a CSS file from the same directory as this script.
7
+ """
8
+ css_file_path = os.path.join(os.path.dirname(__file__), file_name)
9
+ with open(css_file_path) as f:
10
+ st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)