matthewlewis06 commited on
Commit
57d180b
·
1 Parent(s): ddfde87

Updated frontend and cleaned imports

Browse files
nhs_logo.png ADDED
src/__pycache__/config.cpython-312.pyc ADDED
Binary file (1.91 kB). View file
 
src/__pycache__/query_rag.cpython-312.pyc ADDED
Binary file (15 kB). View file
 
src/__pycache__/search_engine.cpython-312.pyc ADDED
Binary file (2.44 kB). View file
 
src/config.py CHANGED
@@ -1,6 +1,4 @@
1
- import os
2
  from enum import Enum
3
- from typing import Dict, NamedTuple
4
  from dataclasses import dataclass
5
 
6
  class InfoSource(Enum):
 
 
1
  from enum import Enum
 
2
  from dataclasses import dataclass
3
 
4
  class InfoSource(Enum):
src/query_rag.py CHANGED
@@ -1,8 +1,6 @@
1
  import os
2
- import time
3
  import argparse
4
  import logging
5
- import re
6
  from typing import Dict, List, Optional, Generator, Tuple
7
  from openai import OpenAI
8
  from config import Config, InfoSource
 
1
  import os
 
2
  import argparse
3
  import logging
 
4
  from typing import Dict, List, Optional, Generator, Tuple
5
  from openai import OpenAI
6
  from config import Config, InfoSource
src/search_engine.py CHANGED
@@ -1,8 +1,5 @@
1
- import numpy as np
2
- import pandas as pd
3
  import voyageai
4
- from typing import List, Dict, Tuple, Optional
5
- from collections import defaultdict
6
  import logging
7
  import os
8
  from pinecone import Pinecone
@@ -18,7 +15,7 @@ class SearchEngine:
18
  self.pc = Pinecone(api_key=pinecone_api_key)
19
  self.index = self.pc.Index("nhs-conditions")
20
 
21
- def similarity_search(self, query_text: str, namespace: str, top_k: int = 25) -> List[dict]:
22
  """Perform similarity search using Pinecone"""
23
  try:
24
  # Embed the query using the same model - matches your example exactly
 
 
 
1
  import voyageai
2
+ from typing import List
 
3
  import logging
4
  import os
5
  from pinecone import Pinecone
 
15
  self.pc = Pinecone(api_key=pinecone_api_key)
16
  self.index = self.pc.Index("nhs-conditions")
17
 
18
+ def similarity_search(self, query_text: str, namespace: str, top_k: int = 5) -> List[dict]:
19
  """Perform similarity search using Pinecone"""
20
  try:
21
  # Embed the query using the same model - matches your example exactly
src/streamlit_app.py CHANGED
@@ -1,5 +1,7 @@
1
  import streamlit as st
2
  from typing import Dict, List
 
 
3
 
4
  try:
5
  from query_rag import RAGSystem
@@ -8,9 +10,24 @@ except ImportError as e:
8
  st.stop()
9
 
10
 
11
- # --- Page Configuration and Initialization ---
12
- st.set_page_config(page_title="NHS Clinical Assistant", layout="wide")
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  # Initialize RAG System
16
  def get_rag_system():
@@ -43,13 +60,11 @@ def display_sources(sources_data: List[Dict]):
43
  clean_section = metadata.get('clean_section', 'Unknown Section')
44
  url = metadata.get('url', '')
45
 
46
- source_text = f"**Source {idx+1}:** {clean_section}"
47
- st.markdown(source_text)
48
-
49
  if url:
50
- st.markdown(f" 🔗 [View Online]({url})")
51
-
52
- st.markdown("---")
53
 
54
 
55
  def initialize_session_state():
@@ -77,25 +92,132 @@ initialize_session_state()
77
  # --- STYLING ---
78
  st.markdown("""
79
  <style>
80
- .main {background-color: #f9f9f9; font-family: Arial, sans-serif;}
81
- h1, h2, h3, h4, h5, h6 {color: #2b6777;}
82
- h1 {font-weight: bold;}
83
- [data-testid="stSidebar"] {background-color: #e8f0fe; padding: 10px;}
84
- .result-box {
85
- border-left: 4px solid #4CAF50;
86
- padding: 10px;
87
- background-color: #fff;
88
- margin-bottom: 10px;
89
- border-radius: 4px;
90
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
91
- }
92
- div.stTextArea > div { border-radius: 8px; }
93
- textarea { font-family: Arial, sans-serif; font-size: 16px; color: #333; resize: vertical; }
94
- .stButton>button { border-radius: 5px; }
95
- div.stSelectbox > label {
96
- font-size: 16px !important;
97
- font-weight: bold !important;
98
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  </style>
100
  """, unsafe_allow_html=True)
101
 
@@ -132,8 +254,45 @@ with st.sidebar:
132
 
133
 
134
  # --- MAIN APPLICATION AREA ---
135
- st.title("🩺 NHS Clinical Assistant")
136
- st.markdown("Ask questions and get relevant information from trusted NHS health condition sources.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  def submit_and_process_query(query_to_send: str, display_query_text: str):
139
  st.session_state.processing_query = True
@@ -154,7 +313,7 @@ def submit_and_process_query(query_to_send: str, display_query_text: str):
154
  sources_data = chunk_sources_data
155
 
156
  temp_response_placeholder.markdown(
157
- f"<div style='border-left: 4px solid #4CAF50; padding-left: 10px;'>{''.join(response_chunks)}</div>",
158
  unsafe_allow_html=True
159
  )
160
 
@@ -177,43 +336,40 @@ def submit_and_process_query(query_to_send: str, display_query_text: str):
177
 
178
  # Display chat history
179
  for i, chat_entry in enumerate(st.session_state.chat_history):
180
- st.markdown(f"👤 **You:** {chat_entry['display_query']}")
 
181
 
182
  response_info = f"(LLM: {chat_entry.get('llm_model', 'N/A')})"
183
 
184
- st.markdown(f"🤖 **Assistant** {response_info}:")
185
- st.markdown(
186
- f"<div style='border-left: 4px solid #4CAF50; padding-left: 10px; margin-bottom: 10px;'>{chat_entry['response']}</div>",
187
- unsafe_allow_html=True
188
- )
189
 
190
- st.subheader("📚 Sources:")
191
  with st.expander("View Sources", expanded=False):
192
  sources_data = chat_entry.get("sources_data", [])
193
  if sources_data:
194
  display_sources(sources_data)
195
  else:
196
  st.markdown("No sources available for this response.")
197
- st.markdown("---")
198
 
199
- # Suggested queries
200
- st.markdown("<h6>�� Suggested Queries:</h6>", unsafe_allow_html=True)
201
  suggested_queries_list = [
202
  "What are the symptoms of ADHD in adults?",
203
  "How is type 2 diabetes diagnosed?",
204
  "What are the treatment options for depression?"
205
  ]
206
- sq_cols = st.columns(len(suggested_queries_list))
207
- for idx, sq_text_item in enumerate(suggested_queries_list):
208
- if sq_cols[idx].button(
209
- sq_text_item,
210
- key=f"suggested_{idx}",
211
- disabled=st.session_state.processing_query
212
- ):
213
- st.session_state.processing_query = True
214
- st.session_state.query_to_run_next = sq_text_item
215
- st.rerun()
216
 
 
 
 
 
 
 
 
 
 
 
217
 
218
  # User input section
219
  user_query = st.chat_input(
@@ -233,6 +389,8 @@ if st.session_state.get("query_to_run_next"):
233
  st.session_state.query_to_run_next = None # Clear it so it doesn't run again
234
  submit_and_process_query(query_to_process, query_to_process)
235
 
 
 
236
  # --- Footer with Licensing Information ---
237
  st.markdown("---")
238
  st.caption("""
 
1
  import streamlit as st
2
  from typing import Dict, List
3
+ from pathlib import Path
4
+ import os
5
 
6
  try:
7
  from query_rag import RAGSystem
 
10
  st.stop()
11
 
12
 
13
+ # --- assets / logo setup ---
 
14
 
15
+ # Get the correct path to the logo file
16
+ current_dir = Path(__file__).parent
17
+ LOGO_PATH = current_dir.parent / "nhs_logo.png"
18
+
19
+ # Check if logo exists, if not use a fallback
20
+ if LOGO_PATH.exists():
21
+ logo_path_str = str(LOGO_PATH)
22
+ else:
23
+ # Fallback if logo doesn't exist
24
+ logo_path_str = None
25
+
26
+ LOGO_ALT = "NHS logo"
27
+
28
+ # set a page icon (use emoji as fallback if logo file doesn't exist)
29
+ page_icon = "🩺" # Use emoji instead of file path for better compatibility
30
+ st.set_page_config(page_title="NHS Clinical Assistant", layout="wide", page_icon=page_icon)
31
 
32
  # Initialize RAG System
33
  def get_rag_system():
 
60
  clean_section = metadata.get('clean_section', 'Unknown Section')
61
  url = metadata.get('url', '')
62
 
63
+ source_text = f"<div class='source-item'><div class='source-title'>Source {idx+1}: {clean_section}</div>"
 
 
64
  if url:
65
+ source_text += f"<div class='source-link'>🔗 <a href='{url}' target='_blank'>View online</a></div>"
66
+ source_text += "</div>"
67
+ st.markdown(source_text, unsafe_allow_html=True)
68
 
69
 
70
  def initialize_session_state():
 
92
  # --- STYLING ---
93
  st.markdown("""
94
  <style>
95
+ /* Import a modern font */
96
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
97
+
98
+ :root{
99
+ --bg:#f6f8fa;
100
+ --card:#ffffff;
101
+ --accent:#1f7a8c; /* deep teal */
102
+ --accent-2:#2b6777;
103
+ --muted:#6b7280;
104
+ --green:#16a34a;
105
+ }
106
+
107
+ html, body, [class*="css"] {
108
+ font-family: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
109
+ color: #0f1724;
110
+ background: linear-gradient(180deg, var(--bg) 0%, #ffffff 100%);
111
+ }
112
+
113
+ /* Main container */
114
+ .main .block-container {
115
+ padding-top: 8px;
116
+ padding-left: 28px;
117
+ padding-right: 28px;
118
+ max-width: 1200px;
119
+ margin: 0 auto;
120
+ }
121
+
122
+ /* Header */
123
+ .app-header{
124
+ display:flex;
125
+ align-items:center;
126
+ justify-content:space-between;
127
+ gap:12px;
128
+ margin-bottom:12px;
129
+ }
130
+ .app-title {
131
+ display:flex;
132
+ align-items:center;
133
+ gap:12px;
134
+ }
135
+ .logo {
136
+ background: linear-gradient(135deg, var(--accent), var(--accent-2));
137
+ color: white;
138
+ width:44px;
139
+ height:44px;
140
+ display:flex;
141
+ align-items:center;
142
+ justify-content:center;
143
+ font-weight:700;
144
+ border-radius:10px;
145
+ box-shadow: 0 6px 18px rgba(43,103,119,0.12);
146
+ }
147
+ .title-text { font-size:20px; font-weight:700; letter-spacing: -0.2px; }
148
+
149
+ /* Card-like chat area */
150
+ .chat-card {
151
+ background: var(--card);
152
+ border-radius: 12px;
153
+ padding: 18px;
154
+ box-shadow: 0 6px 20px rgba(20,23,30,0.04);
155
+ }
156
+
157
+ /* Chat bubbles */
158
+ .chat-bubble {
159
+ max-width:78%;
160
+ padding:12px 14px;
161
+ border-radius:12px;
162
+ margin-bottom:10px;
163
+ line-height:1.4;
164
+ box-shadow: 0 4px 12px rgba(15,23,36,0.04);
165
+ }
166
+ .chat-bubble.user {
167
+ background: linear-gradient(90deg, rgba(255,255,255,1), rgba(255,255,255,1));
168
+ border: 1px solid rgba(16,24,40,0.06);
169
+ margin-left:auto;
170
+ border-bottom-right-radius:6px;
171
+ }
172
+ .chat-bubble.assistant {
173
+ background: linear-gradient(180deg, rgba(31,122,140,0.06), rgba(43,103,119,0.03));
174
+ border-left: 4px solid var(--accent);
175
+ border-bottom-left-radius:6px;
176
+ margin-right:auto;
177
+ }
178
+
179
+ .chat-meta {
180
+ font-size:12px;
181
+ color:var(--muted);
182
+ margin-bottom:6px;
183
+ }
184
+
185
+ /* Sources */
186
+ .source-item { padding:10px 12px; border-radius:8px; background:#fbfcfd; margin-bottom:8px; border:1px solid rgba(16,24,40,0.03); }
187
+ .source-title { font-weight:600; color:var(--accent-2); margin-bottom:4px; }
188
+ .source-link a { color:var(--accent); text-decoration:none; font-weight:600; }
189
+ .source-link a:hover { text-decoration:underline; }
190
+
191
+ /* Buttons / chips */
192
+ .stButton>button {
193
+ border-radius:999px !important;
194
+ padding:8px 14px !important;
195
+ background: linear-gradient(90deg, #ffffff, #f7fafb) !important;
196
+ border: 1px solid rgba(16,24,40,0.06) !important;
197
+ color: #0f1724 !important;
198
+ box-shadow: 0 4px 10px rgba(12,18,28,0.04);
199
+ }
200
+ .stButton>button:active { transform: translateY(1px); }
201
+
202
+ /* Suggested chips */
203
+ .suggested-chips { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:12px; }
204
+ .chip {
205
+ display:inline-flex;
206
+ gap:8px;
207
+ align-items:center;
208
+ padding:8px 12px;
209
+ border-radius:999px;
210
+ background: rgba(47,128,237,0.06);
211
+ color: #164e63;
212
+ border:1px solid rgba(47,128,237,0.12);
213
+ cursor:pointer;
214
+ font-weight:600;
215
+ font-size:13px;
216
+ }
217
+
218
+ /* small helpers */
219
+ .muted { color:var(--muted); font-size:13px; }
220
+ hr.stDivider { border: none; height:1px; background: linear-gradient(90deg, transparent, rgba(15,23,36,0.06), transparent); margin: 16px 0; }
221
  </style>
222
  """, unsafe_allow_html=True)
223
 
 
254
 
255
 
256
  # --- MAIN APPLICATION AREA ---
257
+ # Header area with logo, title and small info
258
+ header_cols = st.columns([0.12, 0.76, 0.12])
259
+ with header_cols[0]:
260
+ if logo_path_str and os.path.exists(logo_path_str):
261
+ # Use HTML to control sizing while preserving quality
262
+ import base64
263
+ with open(logo_path_str, "rb") as img_file:
264
+ img_base64 = base64.b64encode(img_file.read()).decode()
265
+
266
+ st.markdown(f"""
267
+ <div style="display: flex; justify-content: center; align-items: center;">
268
+ <img src="data:image/png;base64,{img_base64}"
269
+ style="max-width: 100%; height: auto; max-height: 60px; object-fit: contain;"
270
+ alt="NHS Logo">
271
+ </div>
272
+ """, unsafe_allow_html=True)
273
+ else:
274
+ # Fallback: display emoji or text if logo not found
275
+ st.markdown('<div style="font-size:56px;">🩺</div>', unsafe_allow_html=True)
276
+
277
+
278
+
279
+ with header_cols[1]:
280
+ st.markdown("""
281
+ <div style="display:flex; flex-direction:column; justify-content:center;">
282
+ <div class="title-text">NHS Clinical Assistant</div>
283
+ <div class="muted">Trusted NHS content · Evidence-backed summaries</div>
284
+ </div>
285
+ """, unsafe_allow_html=True)
286
+
287
+ with header_cols[2]:
288
+ st.markdown(f"""
289
+ <div style="display:flex; gap:12px; align-items:center; justify-content:flex-end;">
290
+ <div style="font-size:13px; color:var(--muted);">Model:</div>
291
+ <div style="font-weight:700; color:var(--accent-2);">{st.session_state.llm_model}</div>
292
+ </div>
293
+ """, unsafe_allow_html=True)
294
+
295
+ st.markdown('<div class="chat-card">', unsafe_allow_html=True)
296
 
297
  def submit_and_process_query(query_to_send: str, display_query_text: str):
298
  st.session_state.processing_query = True
 
313
  sources_data = chunk_sources_data
314
 
315
  temp_response_placeholder.markdown(
316
+ f"<div class='chat-bubble assistant'>{''.join(response_chunks)}</div>",
317
  unsafe_allow_html=True
318
  )
319
 
 
336
 
337
  # Display chat history
338
  for i, chat_entry in enumerate(st.session_state.chat_history):
339
+ # user
340
+ st.markdown(f"<div class='chat-bubble user'><div class='chat-meta'>You</div><div>{chat_entry['display_query']}</div></div>", unsafe_allow_html=True)
341
 
342
  response_info = f"(LLM: {chat_entry.get('llm_model', 'N/A')})"
343
 
344
+ st.markdown(f"<div class='chat-bubble assistant'><div class='chat-meta'>Assistant {response_info}</div><div>{chat_entry['response']}</div></div>", unsafe_allow_html=True)
 
 
 
 
345
 
346
+ st.markdown("<div style='margin-top:8px; font-weight:600;'>📚 Sources</div>", unsafe_allow_html=True)
347
  with st.expander("View Sources", expanded=False):
348
  sources_data = chat_entry.get("sources_data", [])
349
  if sources_data:
350
  display_sources(sources_data)
351
  else:
352
  st.markdown("No sources available for this response.")
353
+ st.markdown('<hr class="stDivider">', unsafe_allow_html=True)
354
 
355
+ # Suggested queries - styled as chips
356
+ st.markdown("<div style='margin-top:6px; margin-bottom:8px; font-weight:600;'>💡 Suggested Queries</div>", unsafe_allow_html=True)
357
  suggested_queries_list = [
358
  "What are the symptoms of ADHD in adults?",
359
  "How is type 2 diabetes diagnosed?",
360
  "What are the treatment options for depression?"
361
  ]
 
 
 
 
 
 
 
 
 
 
362
 
363
+ # Render chips in a row. Use buttons beneath for accessibility & state handling.
364
+ chip_cols = st.columns([1,1,1])
365
+ for idx, sq in enumerate(suggested_queries_list):
366
+ with chip_cols[idx]:
367
+ if st.button(sq, key=f"suggested_{idx}", disabled=st.session_state.processing_query):
368
+ st.session_state.processing_query = True
369
+ st.session_state.query_to_run_next = sq
370
+ st.rerun()
371
+
372
+ st.markdown('<div style="height:8px"></div>', unsafe_allow_html=True)
373
 
374
  # User input section
375
  user_query = st.chat_input(
 
389
  st.session_state.query_to_run_next = None # Clear it so it doesn't run again
390
  submit_and_process_query(query_to_process, query_to_process)
391
 
392
+ st.markdown('</div>', unsafe_allow_html=True)
393
+
394
  # --- Footer with Licensing Information ---
395
  st.markdown("---")
396
  st.caption("""