Commit ·
5fc13d5
1
Parent(s): bac1636
ui improvements with glassmorphic effect
Browse files- app/services/document_ingester.py +1 -1
- ui/.streamlit/config.toml +0 -3
- ui/Home.py +132 -45
- ui/pages/Chat.py +17 -8
- ui/pages/Documents.py +61 -40
- ui/style.css +228 -0
- ui/utils.py +10 -0
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.
|
| 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.
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
st.divider()
|
| 20 |
|
| 21 |
# Key Features Section
|
| 22 |
-
st.
|
| 23 |
|
| 24 |
col1, col2, col3 = st.columns(3)
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
with col1:
|
| 27 |
-
st.
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
with col2:
|
| 35 |
-
st.
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
with col3:
|
| 43 |
-
st.
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
st.divider()
|
| 51 |
|
|
|
|
| 52 |
# How It Works / Getting Started
|
| 53 |
-
st.
|
| 54 |
|
| 55 |
step1, step2, step3 = st.columns(3)
|
| 56 |
|
| 57 |
with step1:
|
| 58 |
-
st.markdown("
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
with step2:
|
| 62 |
-
st.markdown("
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
with step3:
|
| 66 |
-
st.markdown("
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
st.divider()
|
| 70 |
|
| 71 |
# Tech Stack Footer
|
| 72 |
with st.expander("🛠️ Under the Hood"):
|
| 73 |
st.markdown("""
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 15 |
with col_btn:
|
| 16 |
-
if st.button("Clear
|
| 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 |
-
|
| 25 |
-
|
| 26 |
-
st.
|
| 27 |
-
|
| 28 |
|
| 29 |
-
if prompt := st.chat_input("
|
| 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
|
| 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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 64 |
-
|
| 65 |
-
|
| 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 |
-
#
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
| 87 |
|
| 88 |
for idx, doc in enumerate(documents):
|
| 89 |
filename, status, timestamp, path = doc
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
status_color = "green" if status == "ingested" else "orange"
|
| 93 |
status_display = f"✅ {status.capitalize()}" if status == "ingested" else f"⏳ {status.capitalize()}"
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|