ktejeshnaidu commited on
Commit
f590c6d
Β·
verified Β·
1 Parent(s): 5078486

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -283
app.py CHANGED
@@ -1,224 +1,47 @@
1
  """
2
  DocuMind - Streamlit Frontend for HuggingFace Spaces
3
- Minimal & Clean UI implementation based on the provided mockup.
4
  """
5
 
6
  import streamlit as st
7
  import json
 
8
  import time
9
- import requests
10
 
11
  # ============================================================================
12
  # STREAMLIT CONFIG (Must be first)
13
  # ============================================================================
14
-
15
  st.set_page_config(
16
- page_title="DocuMind AI",
17
  page_icon="🧠",
18
  layout="wide",
19
  initial_sidebar_state="expanded"
20
  )
21
 
22
  # ============================================================================
23
- # CUSTOM CSS (Theming to match the minimal clean mockup)
24
  # ============================================================================
25
- custom_css = """
26
- <style>
27
- /* Base Font */
28
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
29
-
30
- html, body, [class*="css"] {
31
- font-family: 'Inter', sans-serif;
32
- }
33
-
34
- /* Main Backgrounds */
35
- .stApp {
36
- background-color: #FAFBFC;
37
- }
38
- [data-testid="stSidebar"] {
39
- background-color: #F4F6F8 !important;
40
- border-right: 1px solid #E5E7EB;
41
- }
42
-
43
- /* Hide top bar & footer */
44
- header {visibility: hidden;}
45
- footer {visibility: hidden;}
46
- .block-container {
47
- padding-top: 2rem !important;
48
- max-width: 900px;
49
- }
50
-
51
- /* Sidebar Logo Header */
52
- .sidebar-header {
53
- display: flex;
54
- align-items: center;
55
- gap: 12px;
56
- margin-bottom: 24px;
57
- margin-top: -30px;
58
- }
59
- .logo-icon {
60
- background-color: #064052;
61
- color: white;
62
- width: 32px;
63
- height: 32px;
64
- border-radius: 8px;
65
- display: flex;
66
- justify-content: center;
67
- align-items: center;
68
- font-weight: bold;
69
- font-size: 16px;
70
- }
71
- .brand-title {
72
- color: #111827;
73
- font-weight: 600;
74
- font-size: 1.1rem;
75
- line-height: 1.2;
76
- }
77
- .brand-subtitle {
78
- color: #9CA3AF;
79
- font-size: 0.75rem;
80
- }
81
-
82
- /* Override New Chat Button */
83
- div[data-testid="stSidebar"] div[data-testid="stButton"] button {
84
- width: 100%;
85
- border-radius: 20px;
86
- background-color: #EFEFEF;
87
- color: #064052;
88
- border: none;
89
- font-weight: 500;
90
- padding: 0.5rem 1rem;
91
- }
92
- div[data-testid="stSidebar"] div[data-testid="stButton"] button:hover {
93
- background-color: #E5E7EB;
94
- }
95
-
96
- /* Override File Uploader to look like the dark Teal Box */
97
- [data-testid="stFileUploader"] {
98
- background-color: #064052;
99
- border-radius: 16px;
100
- padding: 1rem;
101
- margin-top: 1rem;
102
- border: none;
103
- }
104
- [data-testid="stFileUploader"] > section {
105
- text-align: center;
106
- }
107
- [data-testid="stFileUploader"] * {
108
- color: #FFFFFF !important;
109
- }
110
- [data-testid="stFileUploader"] small {
111
- display: none; /* Hide default size limit text */
112
- }
113
-
114
- /* Recents Section */
115
- .recents-title {
116
- font-size: 0.7rem;
117
- font-weight: 600;
118
- color: #9CA3AF;
119
- letter-spacing: 1px;
120
- margin-top: 30px;
121
- margin-bottom: 10px;
122
- }
123
- .recent-item {
124
- color: #4B5563;
125
- font-size: 0.85rem;
126
- padding: 8px 0;
127
- display: flex;
128
- align-items: center;
129
- gap: 10px;
130
- cursor: pointer;
131
- }
132
-
133
- /* Top Bar Indicator */
134
- .session-badge-container {
135
- margin-bottom: 40px;
136
- }
137
- .session-title {
138
- color: #064052;
139
- font-weight: 600;
140
- font-size: 0.9rem;
141
- }
142
- .badge-active {
143
- background-color: #D1E5E8;
144
- color: #064052;
145
- padding: 2px 8px;
146
- border-radius: 12px;
147
- font-size: 0.65rem;
148
- font-weight: 700;
149
- margin-left: 8px;
150
- vertical-align: middle;
151
- }
152
-
153
- /* Empty State Headers */
154
- .welcome-title {
155
- text-align: center;
156
- color: #064052;
157
- font-size: 2.2rem;
158
- font-weight: 700;
159
- margin-top: 2rem;
160
- margin-bottom: 1rem;
161
- }
162
- .welcome-subtitle {
163
- text-align: center;
164
- color: #4B5563;
165
- font-size: 1rem;
166
- margin-bottom: 3rem;
167
- line-height: 1.5;
168
- }
169
-
170
- /* Chat Messages */
171
- [data-testid="stChatMessage"] {
172
- border-radius: 16px;
173
- padding: 1.25rem;
174
- margin-bottom: 1.5rem;
175
- max-width: 85%;
176
- }
177
-
178
- /* Assistant Bubble */
179
- [data-testid="stChatMessage"]:has([data-testid="assistantAvatar"]) {
180
- background-color: #EAEAEA;
181
- color: #111827;
182
- margin-right: auto;
183
- }
184
-
185
- /* User Bubble */
186
- [data-testid="stChatMessage"]:has([data-testid="userAvatar"]) {
187
- background-color: #064052;
188
- color: white;
189
- margin-left: auto;
190
- }
191
- [data-testid="stChatMessage"]:has([data-testid="userAvatar"]) p {
192
- color: white !important;
193
- }
194
-
195
- /* Hide User Avatar to match clean look */
196
- [data-testid="stChatMessage"]:has([data-testid="userAvatar"]) [data-testid="chatAvatarIcon-user"] {
197
- display: none;
198
- }
199
-
200
- /* Chat Input */
201
- [data-testid="stChatInput"] {
202
- background-color: #FFFFFF;
203
- border: 1px solid #E5E7EB;
204
- border-radius: 30px;
205
- padding-left: 10px;
206
- padding-right: 10px;
207
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
208
- }
209
- </style>
210
- """
211
- st.markdown(custom_css, unsafe_allow_html=True)
212
-
213
 
214
  # ============================================================================
215
- # API CONFIGURATION & UTILITIES
216
  # ============================================================================
217
-
218
- API_URL = "http://127.0.0.1:8000"
219
- API_TIMEOUT = 30
 
 
 
 
 
 
 
 
220
 
221
  def safe_api_call(method, endpoint, **kwargs):
 
222
  try:
223
  url = f"{API_URL}{endpoint}"
224
  kwargs.setdefault('timeout', API_TIMEOUT)
@@ -229,118 +52,127 @@ def safe_api_call(method, endpoint, **kwargs):
229
  response = requests.post(url, **kwargs)
230
  else:
231
  return None, "Invalid method"
 
232
  return response, None
 
 
 
 
233
  except Exception as e:
234
  return None, f"❌ Error: {str(e)}"
235
 
236
- # Initialize Session States
237
- if "messages" not in st.session_state:
238
- st.session_state.messages = []
239
-
240
  # ============================================================================
241
- # UI: SIDEBAR
242
  # ============================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  with st.sidebar:
244
- # Top Branding
245
- st.markdown("""
246
- <div class="sidebar-header">
247
- <div class="logo-icon">🧠</div>
248
- <div>
249
- <div class="brand-title">DocuMind AI</div>
250
- <div class="brand-subtitle">Digital Curator</div>
251
- </div>
252
- </div>
253
- """, unsafe_allow_html=True)
254
 
255
- # New Chat
256
- if st.button("οΌ‹ New Chat", use_container_width=True):
257
- st.session_state.messages = []
258
- st.rerun()
259
 
260
- # Upload Area
261
- uploaded_file = st.file_uploader("Upload", type=["txt", "pdf", "docx"], label_visibility="collapsed")
262
- st.markdown("""
263
- <div style="text-align: center; margin-top: -20px; margin-bottom: 20px;">
264
- <span style="color: #8BA8B0; font-size: 0.65rem; font-weight: 600; letter-spacing: 0.5px;">200MB LIMIT</span>
265
- </div>
266
- """, unsafe_allow_html=True)
267
-
268
- if uploaded_file and st.button("Ingest Document", use_container_width=True):
269
- with st.spinner("Processing..."):
270
  files = {"file": (uploaded_file.name, uploaded_file.getvalue())}
271
  response, error = safe_api_call("POST", "/ingest", files=files)
272
- if not error and response and response.status_code == 200:
273
- st.success("βœ… Added to knowledge base!")
 
 
 
274
  else:
275
- st.error("❌ Upload failed.")
276
 
277
- # Recents
278
- st.markdown("<div class='recents-title'>RECENTS</div>", unsafe_allow_html=True)
279
 
 
280
  response, error = safe_api_call("GET", "/sources")
281
- if not error and response and response.status_code == 200:
 
 
 
282
  documents = response.json().get("documents", [])
283
- for doc in documents[:5]:
284
- st.markdown(f"<div class='recent-item'>πŸ•’ {doc[:25]}</div>", unsafe_allow_html=True)
 
 
 
285
  else:
286
- # Static placeholders matching the mockup
287
- st.markdown("<div class='recent-item'>πŸ•’ Q3 Financial Report</div>", unsafe_allow_html=True)
288
- st.markdown("<div class='recent-item'>πŸ•’ Product Roadmap 2...</div>", unsafe_allow_html=True)
289
- st.markdown("<div class='recent-item'>πŸ•’ Research Summary</div>", unsafe_allow_html=True)
290
 
 
 
291
 
292
- # ============================================================================
293
- # UI: MAIN CONTENT
294
- # ============================================================================
295
-
296
- # Top Session Indicator
297
- st.markdown("""
298
- <div class="session-badge-container">
299
- <span class="session-title">CURRENT SESSION</span>
300
- <span class="badge-active">ACTIVE</span>
301
- </div>
302
- """, unsafe_allow_html=True)
303
-
304
- # Empty State
305
- if len(st.session_state.messages) == 0:
306
- st.markdown("<div class='welcome-title'>How can I assist your research today?</div>", unsafe_allow_html=True)
307
- st.markdown("<div class='welcome-subtitle'>I can analyze documents, summarize findings, or answer<br>specific questions about your archived data.</div>", unsafe_allow_html=True)
308
 
309
- # Chat Container
310
  for msg in st.session_state.messages:
311
- is_user = msg["role"] == "user"
312
- avatar = None if is_user else "🧠"
313
-
314
- with st.chat_message(msg["role"], avatar=avatar):
315
  st.markdown(msg["content"])
316
-
317
-
318
- # ============================================================================
319
- # UI: CHAT INPUT
320
- # ============================================================================
321
- user_input = st.chat_input("Ask a question...")
 
 
 
 
 
 
 
 
322
 
323
  if user_input:
324
- # 1. Show user message
325
  st.session_state.messages.append({"role": "user", "content": user_input})
326
- with st.chat_message("user", avatar=None):
 
 
327
  st.markdown(user_input)
328
 
329
- # 2. Process and show assistant response
330
- with st.chat_message("assistant", avatar="🧠"):
331
  placeholder = st.empty()
332
  full_response = ""
 
333
 
334
  response, error = safe_api_call(
335
- "POST",
336
- "/query",
337
- json={"question": user_input},
338
  stream=True
339
  )
340
 
341
  if error:
342
- placeholder.markdown(error)
343
- full_response = error
 
344
  elif response:
345
  try:
346
  for line in response.iter_lines():
@@ -348,18 +180,49 @@ if user_input:
348
  try:
349
  decoded_line = line.decode('utf-8')
350
  data = json.loads(decoded_line)
351
- if data.get("type") == "token":
 
 
 
352
  full_response += data.get("content", "")
353
  placeholder.markdown(full_response + "β–Œ")
354
  except json.JSONDecodeError:
355
  continue
356
 
357
- # Render final response (Replace bullets with checkmarks to match mockup)
358
- final_text = full_response.replace("- ", "βœ… ")
359
- placeholder.markdown(final_text)
360
-
 
 
 
 
 
 
 
 
 
 
 
361
  except Exception as e:
362
- placeholder.markdown(f"❌ Error: {str(e)}")
363
- full_response = f"Error: {str(e)}"
364
-
365
- st.session_state.messages.append({"role": "assistant", "content": full_response})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
  DocuMind - Streamlit Frontend for HuggingFace Spaces
3
+ Simplified version that works with existing FastAPI backend
4
  """
5
 
6
  import streamlit as st
7
  import json
8
+ import os
9
  import time
 
10
 
11
  # ============================================================================
12
  # STREAMLIT CONFIG (Must be first)
13
  # ============================================================================
 
14
  st.set_page_config(
15
+ page_title="DocuMind - Enterprise RAG",
16
  page_icon="🧠",
17
  layout="wide",
18
  initial_sidebar_state="expanded"
19
  )
20
 
21
  # ============================================================================
22
+ # API CONFIGURATION
23
  # ============================================================================
24
+ # Use localhost for internal communication
25
+ API_URL = "http://127.0.0.1:8000"
26
+ API_TIMEOUT = 30 # seconds
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  # ============================================================================
29
+ # UTILITY FUNCTIONS
30
  # ============================================================================
31
+ def check_backend_health(retries=5):
32
+ """Check if backend API is running"""
33
+ for attempt in range(retries):
34
+ try:
35
+ response = requests.get(f"{API_URL}/docs", timeout=5)
36
+ if response.status_code == 200:
37
+ return True
38
+ except:
39
+ if attempt < retries - 1:
40
+ time.sleep(1)
41
+ return False
42
 
43
  def safe_api_call(method, endpoint, **kwargs):
44
+ """Safely call API with error handling"""
45
  try:
46
  url = f"{API_URL}{endpoint}"
47
  kwargs.setdefault('timeout', API_TIMEOUT)
 
52
  response = requests.post(url, **kwargs)
53
  else:
54
  return None, "Invalid method"
55
+
56
  return response, None
57
+ except requests.exceptions.Timeout:
58
+ return None, "⏱️ Request timed out"
59
+ except requests.exceptions.ConnectionError:
60
+ return None, "⚠️ Backend not responding"
61
  except Exception as e:
62
  return None, f"❌ Error: {str(e)}"
63
 
 
 
 
 
64
  # ============================================================================
65
+ # STREAMLIT FRONTEND UI
66
  # ============================================================================
67
+ st.title("🧠 DocuMind")
68
+ st.markdown("**Enterprise Document Intelligence Chatbot**")
69
+ st.markdown("---")
70
+
71
+ # Health check on load
72
+ if "backend_checked" not in st.session_state:
73
+ st.session_state.backend_checked = False
74
+ st.session_state.backend_healthy = False
75
+
76
+ if not st.session_state.backend_checked:
77
+ with st.spinner("πŸ”„ Starting backend..."):
78
+ time.sleep(2) # Give backend time to start
79
+ st.session_state.backend_healthy = check_backend_health()
80
+ st.session_state.backend_checked = True
81
+
82
+ # Show status
83
+ if not st.session_state.backend_healthy:
84
+ st.warning(
85
+ "⚠️ Backend is starting up. This may take 30-60 seconds on first load. "
86
+ "Please refresh the page if you see this message for more than 1 minute."
87
+ )
88
+
89
+ # --- Sidebar for Document Upload ---
90
  with st.sidebar:
91
+ st.header("🏒 Document Knowledge Base")
92
+ st.markdown("Upload PDFs, DOCX, or TXT documents to add them to the system.")
 
 
 
 
 
 
 
 
93
 
94
+ uploaded_file = st.file_uploader("Upload a new document", type=["txt", "pdf", "docx"])
 
 
 
95
 
96
+ if uploaded_file and st.button("Ingest Document", key="ingest_btn"):
97
+ with st.spinner("Ingesting document (creating chunks & embeddings)..."):
 
 
 
 
 
 
 
 
98
  files = {"file": (uploaded_file.name, uploaded_file.getvalue())}
99
  response, error = safe_api_call("POST", "/ingest", files=files)
100
+
101
+ if error:
102
+ st.error(error)
103
+ elif response and response.status_code == 200:
104
+ st.success(f"βœ… {uploaded_file.name} ingested successfully!")
105
  else:
106
+ st.error(f"❌ Failed to ingest: {response.text if response else 'Unknown error'}")
107
 
108
+ st.divider()
 
109
 
110
+ st.subheader("πŸ“„ Indexed Documents")
111
  response, error = safe_api_call("GET", "/sources")
112
+
113
+ if error:
114
+ st.warning(f"Could not fetch documents: {error}")
115
+ elif response and response.status_code == 200:
116
  documents = response.json().get("documents", [])
117
+ if documents:
118
+ for doc in documents:
119
+ st.markdown(f"- `{doc}`")
120
+ else:
121
+ st.info("No documents indexed yet.")
122
  else:
123
+ st.info("Backend not ready yet...")
 
 
 
124
 
125
+ # --- Main Chat Interface ---
126
+ st.subheader("πŸ’¬ Chat with Your Documents")
127
 
128
+ # Initialize session state
129
+ if "messages" not in st.session_state:
130
+ st.session_state.messages = []
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
+ # Display chat history
133
  for msg in st.session_state.messages:
134
+ with st.chat_message(msg["role"]):
 
 
 
135
  st.markdown(msg["content"])
136
+ if "sources" in msg and msg.get("sources"):
137
+ with st.expander("πŸ“š Show Sources"):
138
+ for idx, src in enumerate(msg["sources"]):
139
+ score = src.get('score', 0)
140
+ st.caption(f"**Source {idx+1}** [Relevance: {score:.2%}]")
141
+ st.markdown(f"**From:** `{src.get('source', 'Unknown')}`")
142
+ content = src.get('content', '')
143
+ if len(content) > 500:
144
+ st.markdown(f"> {content[:500]}...")
145
+ else:
146
+ st.markdown(f"> {content}")
147
+
148
+ # Chat input
149
+ user_input = st.chat_input("Ask a question about your documents...")
150
 
151
  if user_input:
152
+ # Add user message to history
153
  st.session_state.messages.append({"role": "user", "content": user_input})
154
+
155
+ # Display user message
156
+ with st.chat_message("user"):
157
  st.markdown(user_input)
158
 
159
+ # Get assistant response
160
+ with st.chat_message("assistant"):
161
  placeholder = st.empty()
162
  full_response = ""
163
+ sources = []
164
 
165
  response, error = safe_api_call(
166
+ "POST",
167
+ "/query",
168
+ json={"question": user_input},
169
  stream=True
170
  )
171
 
172
  if error:
173
+ error_msg = error
174
+ st.error(error_msg)
175
+ full_response = error_msg
176
  elif response:
177
  try:
178
  for line in response.iter_lines():
 
180
  try:
181
  decoded_line = line.decode('utf-8')
182
  data = json.loads(decoded_line)
183
+
184
+ if data.get("type") == "sources":
185
+ sources = data.get("data", [])
186
+ elif data.get("type") == "token":
187
  full_response += data.get("content", "")
188
  placeholder.markdown(full_response + "β–Œ")
189
  except json.JSONDecodeError:
190
  continue
191
 
192
+ placeholder.markdown(full_response)
193
+
194
+ # Display sources if available
195
+ if sources:
196
+ with st.expander("πŸ“š Show Sources"):
197
+ for idx, src in enumerate(sources):
198
+ score = src.get('score', 0)
199
+ st.caption(f"**Source {idx+1}** [Relevance: {score:.2%}]")
200
+ st.markdown(f"**From:** `{src.get('source', 'Unknown')}`")
201
+ content = src.get('content', '')
202
+ if len(content) > 500:
203
+ st.markdown(f"> {content[:500]}...")
204
+ else:
205
+ st.markdown(f"> {content}")
206
+
207
  except Exception as e:
208
+ error_msg = f"❌ Error processing response: {str(e)}"
209
+ placeholder.markdown(error_msg)
210
+ st.error(error_msg)
211
+ full_response = error_msg
212
+
213
+ # Save assistant message
214
+ st.session_state.messages.append({
215
+ "role": "assistant",
216
+ "content": full_response,
217
+ "sources": sources
218
+ })
219
+
220
+ # Footer
221
+ st.divider()
222
+ st.markdown(
223
+ "<div style='text-align: center; color: var(--color-text-secondary); font-size: 0.85em;'>"
224
+ "DocuMind - Enterprise RAG Chatbot | "
225
+ "<a href='https://github.com/TejeshNaiduKona/DocuMind' target='_blank'>GitHub</a>"
226
+ "</div>",
227
+ unsafe_allow_html=True
228
+ )