Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- src/ui/streamlit_app.py +174 -98
src/ui/streamlit_app.py
CHANGED
|
@@ -29,7 +29,7 @@ API_GATEWAY_URL = "http://localhost:8000"
|
|
| 29 |
st.set_page_config(
|
| 30 |
page_title="Doc-Fetch",
|
| 31 |
layout="wide",
|
| 32 |
-
initial_sidebar_state="expanded",
|
| 33 |
)
|
| 34 |
|
| 35 |
# =======================
|
|
@@ -37,47 +37,55 @@ st.set_page_config(
|
|
| 37 |
# =======================
|
| 38 |
st.markdown("""
|
| 39 |
<style>
|
|
|
|
| 40 |
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
| 41 |
|
| 42 |
html, body, [class*="css"] {
|
| 43 |
font-family: 'Inter', sans-serif;
|
| 44 |
-
background-color: #ffffff;
|
| 45 |
-
color: #1f1f1f;
|
| 46 |
}
|
| 47 |
|
| 48 |
-
/*
|
|
|
|
| 49 |
.stTextInput > div[data-baseweb="input"] {
|
| 50 |
background-color: transparent !important;
|
| 51 |
border: none !important;
|
| 52 |
border-radius: 24px !important;
|
| 53 |
box-shadow: none !important;
|
| 54 |
}
|
|
|
|
|
|
|
| 55 |
.stTextInput input {
|
| 56 |
border-radius: 24px !important;
|
| 57 |
-
background-color: #f0f4f9 !important;
|
| 58 |
border: 1px solid transparent !important;
|
| 59 |
color: #1f1f1f !important;
|
| 60 |
padding: 12px 20px !important;
|
| 61 |
font-size: 16px !important;
|
| 62 |
transition: all 0.2s ease;
|
| 63 |
}
|
|
|
|
|
|
|
| 64 |
.stTextInput input:focus {
|
| 65 |
background-color: #ffffff !important;
|
| 66 |
-
border-color: #0b57d0 !important;
|
| 67 |
box-shadow: 0 0 0 2px rgba(11, 87, 208, 0.2) !important;
|
| 68 |
outline: none !important;
|
| 69 |
}
|
| 70 |
|
| 71 |
-
/*
|
| 72 |
.stButton > button {
|
| 73 |
border-radius: 20px;
|
| 74 |
font-weight: 500;
|
| 75 |
border: none;
|
| 76 |
padding: 0.5rem 1.5rem;
|
| 77 |
transition: all 0.3s ease;
|
| 78 |
-
white-space: nowrap;
|
| 79 |
-
min-width: 140px;
|
| 80 |
}
|
|
|
|
|
|
|
| 81 |
button[kind="primary"] {
|
| 82 |
background: linear-gradient(90deg, #4b90ff, #ff5546);
|
| 83 |
color: white;
|
|
@@ -87,18 +95,60 @@ st.markdown("""
|
|
| 87 |
box-shadow: 0 4px 12px rgba(75, 144, 255, 0.3);
|
| 88 |
}
|
| 89 |
|
| 90 |
-
/* Result
|
| 91 |
.result-card {
|
| 92 |
-
background-color: #f0f4f9;
|
| 93 |
border-radius: 16px;
|
| 94 |
padding: 1.5rem;
|
| 95 |
margin-bottom: 1rem;
|
|
|
|
|
|
|
| 96 |
}
|
| 97 |
.result-card:hover {
|
| 98 |
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
| 99 |
}
|
| 100 |
|
| 101 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
.gradient-text {
|
| 103 |
background: linear-gradient(to right, #4285f4, #9b72cb, #d96570);
|
| 104 |
-webkit-background-clip: text;
|
|
@@ -106,12 +156,17 @@ st.markdown("""
|
|
| 106 |
font-weight: 700;
|
| 107 |
font-size: 3rem;
|
| 108 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
</style>
|
| 110 |
""", unsafe_allow_html=True)
|
| 111 |
|
| 112 |
-
|
| 113 |
# =======================
|
| 114 |
-
# SIDEBAR
|
| 115 |
# =======================
|
| 116 |
with st.sidebar:
|
| 117 |
st.markdown("### ⚙️ Settings")
|
|
@@ -121,63 +176,44 @@ with st.sidebar:
|
|
| 121 |
st.subheader(" Evaluation")
|
| 122 |
run_eval = st.button("Run Evaluation Script")
|
| 123 |
st.divider()
|
| 124 |
-
st.caption("Semantic Search · Smart Cache · FAISS Retrieval · Multi-Service Architecture · MiniLM Embeddings
|
| 125 |
|
| 126 |
API_GATEWAY_URL = url_input
|
| 127 |
|
| 128 |
-
|
| 129 |
# =======================
|
| 130 |
-
# MAIN HEADER
|
| 131 |
# =======================
|
| 132 |
col1, col2, col3 = st.columns([1, 6, 1])
|
| 133 |
with col2:
|
|
|
|
| 134 |
st.markdown('<div style="text-align: center; margin-bottom: 10px;"><span class="gradient-text">Hello, Explorer</span></div>', unsafe_allow_html=True)
|
| 135 |
st.markdown('<div style="text-align: center; color: #444746; font-size: 1.2rem; margin-bottom: 30px;">How can I help you find documents today?</div>', unsafe_allow_html=True)
|
| 136 |
|
| 137 |
|
| 138 |
# =======================
|
| 139 |
-
# SEARCH BAR
|
| 140 |
# =======================
|
|
|
|
| 141 |
sc1, sc2, sc3 = st.columns([1, 4, 1])
|
| 142 |
|
| 143 |
-
# =======================
|
| 144 |
-
# DISABLE AUTOCOMPLETE (YOUR PATCH)
|
| 145 |
-
# =======================
|
| 146 |
-
st.markdown("""
|
| 147 |
-
<style>
|
| 148 |
-
input[type=text] { autocomplete: off !important; }
|
| 149 |
-
</style>
|
| 150 |
-
|
| 151 |
-
<script>
|
| 152 |
-
document.addEventListener('DOMContentLoaded', function() {
|
| 153 |
-
const inputs = window.parent.document.querySelectorAll('input[type="text"]');
|
| 154 |
-
inputs.forEach(inp => {
|
| 155 |
-
inp.setAttribute('autocomplete', 'off');
|
| 156 |
-
inp.setAttribute('autocorrect', 'off');
|
| 157 |
-
inp.setAttribute('autocapitalize', 'off');
|
| 158 |
-
inp.setAttribute('spellcheck', 'false');
|
| 159 |
-
});
|
| 160 |
-
});
|
| 161 |
-
</script>
|
| 162 |
-
""", unsafe_allow_html=True)
|
| 163 |
-
|
| 164 |
with sc2:
|
| 165 |
query = st.text_input(
|
| 166 |
-
"Search Query",
|
| 167 |
placeholder="Ask a question about your documents...",
|
| 168 |
label_visibility="collapsed"
|
| 169 |
)
|
| 170 |
-
|
|
|
|
| 171 |
b1, b2, b3 = st.columns([2, 1, 2])
|
| 172 |
with b2:
|
| 173 |
submit_btn = st.button("Sparkle Search", type="primary", use_container_width=True)
|
| 174 |
|
| 175 |
-
|
| 176 |
# =======================
|
| 177 |
# SEARCH HANDLER
|
| 178 |
# =======================
|
| 179 |
if submit_btn and query.strip():
|
| 180 |
|
|
|
|
| 181 |
with st.spinner("✨ Analyzing semantics..."):
|
| 182 |
|
| 183 |
response = requests.post(
|
|
@@ -189,18 +225,23 @@ if submit_btn and query.strip():
|
|
| 189 |
st.error(f"❌ Connection Error: {response.text}")
|
| 190 |
st.stop()
|
| 191 |
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
if "results" not in data:
|
| 195 |
-
st.info("No relevant documents found.")
|
| 196 |
st.stop()
|
| 197 |
|
|
|
|
| 198 |
st.markdown("### ✨ Search Results")
|
| 199 |
st.markdown("---")
|
| 200 |
|
| 201 |
-
#
|
| 202 |
-
#
|
| 203 |
-
#
|
| 204 |
for item in data["results"]:
|
| 205 |
filename = item["filename"]
|
| 206 |
score = item["score"]
|
|
@@ -209,102 +250,134 @@ if submit_btn and query.strip():
|
|
| 209 |
full_text = item["full_text"]
|
| 210 |
|
| 211 |
safe_preview = html.escape(preview)
|
| 212 |
-
|
|
|
|
| 213 |
keywords = explanation.get("keyword_overlap", [])
|
| 214 |
-
keyword_html = ""
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
st.markdown(f"""
|
| 217 |
<div class="result-card">
|
| 218 |
-
<div style="display:flex; justify-content:space-between;">
|
| 219 |
<div class="card-title">
|
| 220 |
-
|
| 221 |
</div>
|
| 222 |
<div class="score-badge">match: {score:.4f}</div>
|
| 223 |
</div>
|
| 224 |
<p class="card-preview">{safe_preview}...</p>
|
| 225 |
-
<div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
</div>
|
| 227 |
""", unsafe_allow_html=True)
|
| 228 |
|
| 229 |
-
#
|
| 230 |
with st.expander(f"View Document Insights: Semantic Overlap, Top Sentences, LLM Reasoning & Full Text for {filename}"):
|
| 231 |
-
|
| 232 |
overlap_ratio = explanation.get("overlap_ratio", 0)
|
| 233 |
sentences = explanation.get("top_sentences", [])
|
| 234 |
-
|
| 235 |
st.caption(f"Semantic Overlap Ratio: {overlap_ratio:.3f}")
|
| 236 |
-
|
| 237 |
if sentences:
|
| 238 |
st.markdown("**Key Excerpts:**")
|
| 239 |
for s in sentences:
|
|
|
|
| 240 |
st.markdown(f"""
|
| 241 |
-
<div style=
|
| 242 |
-
"{s['sentence']}"
|
| 243 |
-
<span style=
|
| 244 |
</div>
|
| 245 |
""", unsafe_allow_html=True)
|
| 246 |
-
|
| 247 |
llm_expl = explanation.get("llm_explanation")
|
| 248 |
if llm_expl:
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
st.markdown("---")
|
| 253 |
st.markdown("**📄 Full Document Content:**")
|
| 254 |
-
st.code(full_text, language="text")
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
# =======================
|
| 258 |
-
# EVALUATION SECTION
|
| 259 |
-
# =======================
|
| 260 |
if run_eval:
|
| 261 |
|
| 262 |
-
st.info("Running evaluation...")
|
| 263 |
|
| 264 |
results = run_evaluation(top_k=10)
|
| 265 |
|
| 266 |
st.success("Evaluation Complete!")
|
| 267 |
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
c1, c2, c3, c4 = st.columns(4)
|
| 271 |
-
with c1:
|
| 272 |
-
|
| 273 |
-
with
|
| 274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
st.markdown("---")
|
| 277 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
wrong = [d for d in results["details"] if not d["is_correct"]]
|
| 279 |
-
st.markdown("## Incorrect Fetches")
|
| 280 |
|
| 281 |
if wrong:
|
| 282 |
for item in wrong:
|
| 283 |
st.markdown(f"""
|
| 284 |
-
<div style="
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
|
|
|
|
|
|
|
|
|
| 288 |
<b>Expected:</b> {item['expected']}<br>
|
| 289 |
<b>Retrieved:</b> {item['retrieved']}<br>
|
| 290 |
<b>Rank:</b> {item['rank']}
|
| 291 |
</div>
|
| 292 |
""", unsafe_allow_html=True)
|
| 293 |
else:
|
| 294 |
-
st.success("No incorrect queries!")
|
| 295 |
|
| 296 |
st.markdown("---")
|
| 297 |
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
correct_items = [d for d in results["details"] if d["is_correct"]]
|
| 300 |
|
| 301 |
if correct_items:
|
| 302 |
for item in correct_items:
|
| 303 |
st.markdown(f"""
|
| 304 |
-
<div style="
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
| 308 |
<b>Expected:</b> {item['expected']}<br>
|
| 309 |
<b>Top-K Retrieved:</b> {item['retrieved']}<br>
|
| 310 |
<b>Rank:</b> {item['rank']}
|
|
@@ -315,16 +388,19 @@ if run_eval:
|
|
| 315 |
|
| 316 |
st.markdown("---")
|
| 317 |
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
|
|
|
|
|
|
|
|
|
| 329 |
|
| 330 |
st.dataframe(table_data, use_container_width=True)
|
|
|
|
| 29 |
st.set_page_config(
|
| 30 |
page_title="Doc-Fetch",
|
| 31 |
layout="wide",
|
| 32 |
+
initial_sidebar_state="expanded", # Changed from "collapsed" to "expanded"
|
| 33 |
)
|
| 34 |
|
| 35 |
# =======================
|
|
|
|
| 37 |
# =======================
|
| 38 |
st.markdown("""
|
| 39 |
<style>
|
| 40 |
+
/* Global Font & Background */
|
| 41 |
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
| 42 |
|
| 43 |
html, body, [class*="css"] {
|
| 44 |
font-family: 'Inter', sans-serif;
|
| 45 |
+
background-color: #ffffff; /* White Background */
|
| 46 |
+
color: #1f1f1f; /* Dark text for contrast */
|
| 47 |
}
|
| 48 |
|
| 49 |
+
/* --- INPUT FIELD FIX --- */
|
| 50 |
+
/* 1. Remove the default Streamlit border/background on the container */
|
| 51 |
.stTextInput > div[data-baseweb="input"] {
|
| 52 |
background-color: transparent !important;
|
| 53 |
border: none !important;
|
| 54 |
border-radius: 24px !important;
|
| 55 |
box-shadow: none !important;
|
| 56 |
}
|
| 57 |
+
|
| 58 |
+
/* 2. Style the actual input element */
|
| 59 |
.stTextInput input {
|
| 60 |
border-radius: 24px !important;
|
| 61 |
+
background-color: #f0f4f9 !important; /* Light ash input */
|
| 62 |
border: 1px solid transparent !important;
|
| 63 |
color: #1f1f1f !important;
|
| 64 |
padding: 12px 20px !important;
|
| 65 |
font-size: 16px !important;
|
| 66 |
transition: all 0.2s ease;
|
| 67 |
}
|
| 68 |
+
|
| 69 |
+
/* 3. Focus state - clean blue border, no default red overlay */
|
| 70 |
.stTextInput input:focus {
|
| 71 |
background-color: #ffffff !important;
|
| 72 |
+
border-color: #0b57d0 !important; /* Gemini Blue */
|
| 73 |
box-shadow: 0 0 0 2px rgba(11, 87, 208, 0.2) !important;
|
| 74 |
outline: none !important;
|
| 75 |
}
|
| 76 |
|
| 77 |
+
/* Button Styling */
|
| 78 |
.stButton > button {
|
| 79 |
border-radius: 20px;
|
| 80 |
font-weight: 500;
|
| 81 |
border: none;
|
| 82 |
padding: 0.5rem 1.5rem;
|
| 83 |
transition: all 0.3s ease;
|
| 84 |
+
white-space: nowrap; /* Forces text to stay on one line */
|
| 85 |
+
min-width: 140px; /* Ensures button is never too skinny */
|
| 86 |
}
|
| 87 |
+
|
| 88 |
+
/* Primary Search Button */
|
| 89 |
button[kind="primary"] {
|
| 90 |
background: linear-gradient(90deg, #4b90ff, #ff5546);
|
| 91 |
color: white;
|
|
|
|
| 95 |
box-shadow: 0 4px 12px rgba(75, 144, 255, 0.3);
|
| 96 |
}
|
| 97 |
|
| 98 |
+
/* Result Card - Light Ash Background */
|
| 99 |
.result-card {
|
| 100 |
+
background-color: #f0f4f9; /* Light Ash */
|
| 101 |
border-radius: 16px;
|
| 102 |
padding: 1.5rem;
|
| 103 |
margin-bottom: 1rem;
|
| 104 |
+
border: none; /* Removed border for cleaner look on light mode */
|
| 105 |
+
transition: transform 0.2s;
|
| 106 |
}
|
| 107 |
.result-card:hover {
|
| 108 |
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
| 109 |
}
|
| 110 |
|
| 111 |
+
/* Typography in Cards */
|
| 112 |
+
.card-title {
|
| 113 |
+
color: #1f1f1f; /* Dark Title */
|
| 114 |
+
font-size: 1.1rem;
|
| 115 |
+
font-weight: 600;
|
| 116 |
+
margin-bottom: 0.5rem;
|
| 117 |
+
display: flex;
|
| 118 |
+
align-items: center;
|
| 119 |
+
gap: 8px;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.card-preview {
|
| 123 |
+
color: #444746; /* Darker gray for readable preview */
|
| 124 |
+
font-size: 0.95rem;
|
| 125 |
+
line-height: 1.5;
|
| 126 |
+
margin-bottom: 1rem;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
/* Pills & Badges */
|
| 130 |
+
.score-badge {
|
| 131 |
+
background-color: #c4eed0; /* Light Green bg */
|
| 132 |
+
color: #0f5223; /* Dark Green text */
|
| 133 |
+
padding: 4px 12px;
|
| 134 |
+
border-radius: 12px;
|
| 135 |
+
font-size: 0.75rem;
|
| 136 |
+
font-weight: 500;
|
| 137 |
+
display: inline-block;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
.keyword-pill {
|
| 141 |
+
background-color: #c2e7ff; /* Light Blue bg */
|
| 142 |
+
color: #004a77; /* Dark Blue text */
|
| 143 |
+
padding: 2px 10px;
|
| 144 |
+
border-radius: 8px;
|
| 145 |
+
font-size: 0.8rem;
|
| 146 |
+
margin-right: 6px;
|
| 147 |
+
display: inline-block;
|
| 148 |
+
margin-bottom: 4px;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
/* Gradient Text for Header */
|
| 152 |
.gradient-text {
|
| 153 |
background: linear-gradient(to right, #4285f4, #9b72cb, #d96570);
|
| 154 |
-webkit-background-clip: text;
|
|
|
|
| 156 |
font-weight: 700;
|
| 157 |
font-size: 3rem;
|
| 158 |
}
|
| 159 |
+
|
| 160 |
+
/* Custom Info Box */
|
| 161 |
+
.stAlert {
|
| 162 |
+
background-color: #f0f4f9;
|
| 163 |
+
color: #1f1f1f;
|
| 164 |
+
}
|
| 165 |
</style>
|
| 166 |
""", unsafe_allow_html=True)
|
| 167 |
|
|
|
|
| 168 |
# =======================
|
| 169 |
+
# SIDEBAR (Settings)
|
| 170 |
# =======================
|
| 171 |
with st.sidebar:
|
| 172 |
st.markdown("### ⚙️ Settings")
|
|
|
|
| 176 |
st.subheader(" Evaluation")
|
| 177 |
run_eval = st.button("Run Evaluation Script")
|
| 178 |
st.divider()
|
| 179 |
+
st.caption("Semantic Search · Smart Cache · FAISS Retrieval · Multi-Service Architecture · Relevance by MiniLM Embeddings. Reasoning by LLM.")
|
| 180 |
|
| 181 |
API_GATEWAY_URL = url_input
|
| 182 |
|
|
|
|
| 183 |
# =======================
|
| 184 |
+
# MAIN HEADER (Gemini Style)
|
| 185 |
# =======================
|
| 186 |
col1, col2, col3 = st.columns([1, 6, 1])
|
| 187 |
with col2:
|
| 188 |
+
# Use HTML for the gradient text title
|
| 189 |
st.markdown('<div style="text-align: center; margin-bottom: 10px;"><span class="gradient-text">Hello, Explorer</span></div>', unsafe_allow_html=True)
|
| 190 |
st.markdown('<div style="text-align: center; color: #444746; font-size: 1.2rem; margin-bottom: 30px;">How can I help you find documents today?</div>', unsafe_allow_html=True)
|
| 191 |
|
| 192 |
|
| 193 |
# =======================
|
| 194 |
+
# SEARCH BAR CENTERED
|
| 195 |
# =======================
|
| 196 |
+
# Centering the search bar using columns
|
| 197 |
sc1, sc2, sc3 = st.columns([1, 4, 1])
|
| 198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
with sc2:
|
| 200 |
query = st.text_input(
|
| 201 |
+
"Search Query", # Label hidden by CSS/Config if needed, or set visibility hidden
|
| 202 |
placeholder="Ask a question about your documents...",
|
| 203 |
label_visibility="collapsed"
|
| 204 |
)
|
| 205 |
+
|
| 206 |
+
# Buttons row
|
| 207 |
b1, b2, b3 = st.columns([2, 1, 2])
|
| 208 |
with b2:
|
| 209 |
submit_btn = st.button("Sparkle Search", type="primary", use_container_width=True)
|
| 210 |
|
|
|
|
| 211 |
# =======================
|
| 212 |
# SEARCH HANDLER
|
| 213 |
# =======================
|
| 214 |
if submit_btn and query.strip():
|
| 215 |
|
| 216 |
+
# Gemini-style spinner
|
| 217 |
with st.spinner("✨ Analyzing semantics..."):
|
| 218 |
|
| 219 |
response = requests.post(
|
|
|
|
| 225 |
st.error(f"❌ Connection Error: {response.text}")
|
| 226 |
st.stop()
|
| 227 |
|
| 228 |
+
try:
|
| 229 |
+
data = response.json()
|
| 230 |
+
except:
|
| 231 |
+
st.error("❌ Invalid JSON response.")
|
| 232 |
+
st.stop()
|
| 233 |
|
| 234 |
if "results" not in data:
|
| 235 |
+
st.info("No relevant documents found for that query.")
|
| 236 |
st.stop()
|
| 237 |
|
| 238 |
+
# Results Header
|
| 239 |
st.markdown("### ✨ Search Results")
|
| 240 |
st.markdown("---")
|
| 241 |
|
| 242 |
+
# =======================
|
| 243 |
+
# DISPLAY RESULTS (Card Style)
|
| 244 |
+
# =======================
|
| 245 |
for item in data["results"]:
|
| 246 |
filename = item["filename"]
|
| 247 |
score = item["score"]
|
|
|
|
| 250 |
full_text = item["full_text"]
|
| 251 |
|
| 252 |
safe_preview = html.escape(preview)
|
| 253 |
+
|
| 254 |
+
# Prepare keyword HTML
|
| 255 |
keywords = explanation.get("keyword_overlap", [])
|
| 256 |
+
keyword_html = ""
|
| 257 |
+
if keywords:
|
| 258 |
+
keyword_html = "".join([f"<span class='keyword-pill'>{kw}</span>" for kw in keywords])
|
| 259 |
+
|
| 260 |
+
# Doc Icon (SVG) - Changed stroke to dark blue for visibility on light bg
|
| 261 |
+
doc_icon = """<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#0b57d0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>"""
|
| 262 |
+
|
| 263 |
+
# Main Card Render
|
| 264 |
st.markdown(f"""
|
| 265 |
<div class="result-card">
|
| 266 |
+
<div style="display:flex; justify-content:space-between; align-items:start;">
|
| 267 |
<div class="card-title">
|
| 268 |
+
{doc_icon} {filename}
|
| 269 |
</div>
|
| 270 |
<div class="score-badge">match: {score:.4f}</div>
|
| 271 |
</div>
|
| 272 |
<p class="card-preview">{safe_preview}...</p>
|
| 273 |
+
<div style="margin-top: 10px;">
|
| 274 |
+
<div style="font-weight:600; color:#1f1f1f; margin-bottom:6px;">
|
| 275 |
+
Keyword Overlap:
|
| 276 |
+
</div>
|
| 277 |
+
{keyword_html}
|
| 278 |
+
</div>
|
| 279 |
</div>
|
| 280 |
""", unsafe_allow_html=True)
|
| 281 |
|
| 282 |
+
# Details Expander (Standard Streamlit but styled via global CSS)
|
| 283 |
with st.expander(f"View Document Insights: Semantic Overlap, Top Sentences, LLM Reasoning & Full Text for {filename}"):
|
| 284 |
+
|
| 285 |
overlap_ratio = explanation.get("overlap_ratio", 0)
|
| 286 |
sentences = explanation.get("top_sentences", [])
|
| 287 |
+
|
| 288 |
st.caption(f"Semantic Overlap Ratio: {overlap_ratio:.3f}")
|
| 289 |
+
|
| 290 |
if sentences:
|
| 291 |
st.markdown("**Key Excerpts:**")
|
| 292 |
for s in sentences:
|
| 293 |
+
# Updated quote box for light mode
|
| 294 |
st.markdown(f"""
|
| 295 |
+
<div style="background: #ffffff; border-left: 3px solid #4285f4; padding: 10px; margin-bottom: 5px; border-radius: 0 8px 8px 0; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
|
| 296 |
+
<span style="color: #1f1f1f;">"{s['sentence']}"</span>
|
| 297 |
+
<span style="color: #5e5e5e; font-size: 0.8em; margin-left: 10px;">(conf: {s['score']:.2f})</span>
|
| 298 |
</div>
|
| 299 |
""", unsafe_allow_html=True)
|
|
|
|
| 300 |
llm_expl = explanation.get("llm_explanation")
|
| 301 |
if llm_expl:
|
| 302 |
+
st.markdown("**Why this document?**")
|
| 303 |
+
st.write(llm_expl)
|
|
|
|
| 304 |
st.markdown("---")
|
| 305 |
st.markdown("**📄 Full Document Content:**")
|
| 306 |
+
st.code(full_text, language="text") # Using code block for better readability of raw text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
if run_eval:
|
| 308 |
|
| 309 |
+
st.info("Running evaluation... this may take 10–20 seconds...")
|
| 310 |
|
| 311 |
results = run_evaluation(top_k=10)
|
| 312 |
|
| 313 |
st.success("Evaluation Complete!")
|
| 314 |
|
| 315 |
+
# -----------------------------
|
| 316 |
+
# Summary Metrics (Horizontal)
|
| 317 |
+
# -----------------------------
|
| 318 |
+
st.markdown("## Evaluation Summary")
|
| 319 |
|
| 320 |
c1, c2, c3, c4 = st.columns(4)
|
| 321 |
+
with c1:
|
| 322 |
+
st.metric("Accuracy", f"{results['accuracy']}%")
|
| 323 |
+
with c2:
|
| 324 |
+
st.metric("MRR", results["mrr"])
|
| 325 |
+
with c3:
|
| 326 |
+
st.metric("NDCG", results["ndcg"])
|
| 327 |
+
with c4:
|
| 328 |
+
st.metric("Queries", results["total_queries"])
|
| 329 |
+
|
| 330 |
+
st.markdown(
|
| 331 |
+
f"**Correct:** {results['correct_count']} | "
|
| 332 |
+
f"**Incorrect:** {results['incorrect_count']}"
|
| 333 |
+
)
|
| 334 |
|
| 335 |
st.markdown("---")
|
| 336 |
|
| 337 |
+
# -----------------------------
|
| 338 |
+
# Incorrect Results
|
| 339 |
+
# -----------------------------
|
| 340 |
+
st.markdown("## Incorrect Fetches ")
|
| 341 |
+
|
| 342 |
wrong = [d for d in results["details"] if not d["is_correct"]]
|
|
|
|
| 343 |
|
| 344 |
if wrong:
|
| 345 |
for item in wrong:
|
| 346 |
st.markdown(f"""
|
| 347 |
+
<div style="
|
| 348 |
+
padding:14px;
|
| 349 |
+
background:#ffe5e5;
|
| 350 |
+
border-left:5px solid #ff4d4f;
|
| 351 |
+
border-radius:8px;
|
| 352 |
+
margin-bottom:10px;">
|
| 353 |
+
<b> Query:</b> {item['query']}<br>
|
| 354 |
<b>Expected:</b> {item['expected']}<br>
|
| 355 |
<b>Retrieved:</b> {item['retrieved']}<br>
|
| 356 |
<b>Rank:</b> {item['rank']}
|
| 357 |
</div>
|
| 358 |
""", unsafe_allow_html=True)
|
| 359 |
else:
|
| 360 |
+
st.success(" No incorrect queries!")
|
| 361 |
|
| 362 |
st.markdown("---")
|
| 363 |
|
| 364 |
+
# -----------------------------
|
| 365 |
+
# Correct Results
|
| 366 |
+
# -----------------------------
|
| 367 |
+
st.markdown("## Correct Fetches")
|
| 368 |
+
|
| 369 |
correct_items = [d for d in results["details"] if d["is_correct"]]
|
| 370 |
|
| 371 |
if correct_items:
|
| 372 |
for item in correct_items:
|
| 373 |
st.markdown(f"""
|
| 374 |
+
<div style="
|
| 375 |
+
padding:14px;
|
| 376 |
+
background:#e8ffe5;
|
| 377 |
+
border-left:5px solid #2ecc71;
|
| 378 |
+
border-radius:8px;
|
| 379 |
+
margin-bottom:10px;">
|
| 380 |
+
<b> Query:</b> {item['query']}<br>
|
| 381 |
<b>Expected:</b> {item['expected']}<br>
|
| 382 |
<b>Top-K Retrieved:</b> {item['retrieved']}<br>
|
| 383 |
<b>Rank:</b> {item['rank']}
|
|
|
|
| 388 |
|
| 389 |
st.markdown("---")
|
| 390 |
|
| 391 |
+
# -----------------------------
|
| 392 |
+
# Full Table
|
| 393 |
+
# -----------------------------
|
| 394 |
+
st.markdown("## Full Evaluation Table")
|
| 395 |
+
|
| 396 |
+
table_data = []
|
| 397 |
+
for item in results["details"]:
|
| 398 |
+
table_data.append({
|
| 399 |
+
"Query": item["query"],
|
| 400 |
+
"Expected Doc": item["expected"],
|
| 401 |
+
"Retrieved (Top-10)": ", ".join(item["retrieved"]),
|
| 402 |
+
"Correct?": "Yes" if item["is_correct"] else "No",
|
| 403 |
+
"Rank": item["rank"]
|
| 404 |
+
})
|
| 405 |
|
| 406 |
st.dataframe(table_data, use_container_width=True)
|