Clocksp commited on
Commit
9899ca4
·
verified ·
1 Parent(s): d9796eb

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +310 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,312 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import os
3
+ import tempfile
4
+ from pathlib import Path
5
+ from typing import List, Dict, Any
6
+ from utils.pdf_processor import PDFProcessor
7
+ from utils.vector_store import VectorStoreManager
8
+ from utils.rag_chain import InsuranceRAGChain
9
+ from config import Config
10
 
11
+ # Page configuration
12
+ st.set_page_config(
13
+ page_title="Insurance Helper",
14
+ layout="wide",
15
+ initial_sidebar_state="expanded"
16
+ )
17
+
18
+ # Custom CSS
19
+ st.markdown("""
20
+ <style>
21
+ .main-header {
22
+ font-size: 2.5rem;
23
+ font-weight: bold;
24
+ color: #1f77b4;
25
+ text-align: center;
26
+ margin-bottom: 1rem;
27
+ }
28
+ .sub-header {
29
+ font-size: 1.2rem;
30
+ color: #666;
31
+ text-align: center;
32
+ margin-bottom: 2rem;
33
+ }
34
+ .source-box {
35
+ background-color: #f0f2f6;
36
+ padding: 1rem;
37
+ border-radius: 0.5rem;
38
+ margin: 0.5rem 0;
39
+ }
40
+ .success-msg {
41
+ color: #28a745;
42
+ font-weight: bold;
43
+ }
44
+ .error-msg {
45
+ color: #dc3545;
46
+ font-weight: bold;
47
+ }
48
+ </style>
49
+ """, unsafe_allow_html=True)
50
+
51
+
52
+ def initialize_session_state():
53
+ """Initialize session state variables"""
54
+ if 'messages' not in st.session_state:
55
+ st.session_state.messages = []
56
+
57
+ if 'uploaded_files' not in st.session_state:
58
+ st.session_state.uploaded_files = []
59
+
60
+ if 'documents_processed' not in st.session_state:
61
+ st.session_state.documents_processed = True #
62
+
63
+ if 'pdf_processor' not in st.session_state:
64
+ st.session_state.pdf_processor = PDFProcessor()
65
+
66
+ if 'vs_manager' not in st.session_state:
67
+ try:
68
+ st.session_state.vs_manager = VectorStoreManager()
69
+ except Exception as e:
70
+ st.error(f"Failed to initialize Vector Store: {str(e)}")
71
+ st.stop()
72
+
73
+ if 'rag_chain' not in st.session_state:
74
+ st.session_state.rag_chain = InsuranceRAGChain(st.session_state.vs_manager)
75
+
76
+ if 'collection_created' not in st.session_state:
77
+ st.session_state.collection_created = True
78
+
79
+
80
+ def detect_query_intent(question: str) -> str:
81
+ """
82
+ Automatically detect the intent of the user's question
83
+
84
+ Args:
85
+ question: User's question
86
+
87
+ Returns:
88
+ Query mode string
89
+ """
90
+ question_lower = question.lower()
91
+
92
+ # Check for add-on related queries
93
+ addon_keywords = ['addon', 'add-on', 'rider', 'optional cover', 'additional cover',
94
+ 'recommend', 'should i take', 'which cover', 'extra protection']
95
+ if any(keyword in question_lower for keyword in addon_keywords):
96
+ return "addons"
97
+
98
+ # Check for exclusion related queries
99
+ exclusion_keywords = ['exclusion', 'not covered', 'does not cover', "doesn't cover",
100
+ 'what is excluded', 'not include', 'gap', 'missing']
101
+ if any(keyword in question_lower for keyword in exclusion_keywords):
102
+ return "exclusions"
103
+
104
+ # Check for coverage related queries
105
+ coverage_keywords = ['what is covered', 'coverage', 'what does it cover', 'included',
106
+ 'protection', 'insured for', 'claim for']
107
+ if any(keyword in question_lower for keyword in coverage_keywords):
108
+ return "coverage"
109
+
110
+ # Check for term explanation queries
111
+ term_keywords = ['explain', 'what is', 'what does', 'meaning of', 'define',
112
+ 'idv', 'ncb', 'depreciation', 'premium', 'term']
113
+ if any(keyword in question_lower for keyword in term_keywords):
114
+ return "terms"
115
+
116
+ # Default to general query
117
+ return "general"
118
+
119
+
120
+ def save_uploaded_file(uploaded_file) -> str:
121
+ """Save uploaded file to temporary directory"""
122
+ try:
123
+ temp_dir = tempfile.gettempdir()
124
+ file_path = os.path.join(temp_dir, uploaded_file.name)
125
+
126
+ with open(file_path, "wb") as f:
127
+ f.write(uploaded_file.getbuffer())
128
+
129
+ return file_path
130
+ except Exception as e:
131
+ st.error(f"Error saving file: {str(e)}")
132
+ return None
133
+
134
+
135
+ def process_pdfs(file_paths: List[str]) -> bool:
136
+ """Process PDFs and add to vector store"""
137
+ try:
138
+ with st.spinner("Processing PDFs..."):
139
+ # Process all PDFs
140
+ all_chunks, all_metadata = st.session_state.pdf_processor.process_multiple_pdfs(file_paths)
141
+
142
+ if not all_chunks:
143
+ st.error("No content extracted from PDFs")
144
+ return False
145
+
146
+ # Create collection if not exists
147
+ if not st.session_state.collection_created:
148
+ st.session_state.vs_manager.create_collection(recreate=False)
149
+ st.session_state.collection_created = True
150
+
151
+ # Add documents to vector store
152
+ st.session_state.vs_manager.add_documents(all_chunks)
153
+
154
+ st.success(f"Successfully processed {len(file_paths)} PDF(s) with {len(all_chunks)} chunks")
155
+ return True
156
+
157
+ except Exception as e:
158
+ st.error(f"Error processing PDFs: {str(e)}")
159
+ return False
160
+
161
+
162
+ def display_message(message: Dict[str, Any]):
163
+ """Display a chat message"""
164
+ with st.chat_message(message["role"]):
165
+ st.markdown(message["content"])
166
+
167
+ # Display sources if available
168
+ if "sources" in message and message["sources"]:
169
+ with st.expander(f"View {len(message['sources'])} Sources"):
170
+ for source in message["sources"]:
171
+ st.markdown(f"""
172
+ <div class="source-box">
173
+ <strong>Source {source['index']}:</strong> {source['source_file']} (Page {source['page']})<br>
174
+ <strong>Section:</strong> {source['section_type']}<br>
175
+ <strong>Preview:</strong> {source['content_preview']}
176
+ </div>
177
+ """, unsafe_allow_html=True)
178
+
179
+
180
+ def sidebar():
181
+ """Render sidebar with controls"""
182
+ with st.sidebar:
183
+
184
+ # Smart mode indicator
185
+ st.markdown("#### Smart Mode")
186
+ st.info("The system automatically detects your question type and uses the best search strategy!")
187
+
188
+ st.markdown("---")
189
+
190
+ # Show what queries trigger what modes
191
+ with st.expander("How Smart Mode Works"):
192
+ st.markdown("""
193
+ **Exclusions Mode**
194
+ - "What's not covered?"
195
+ - "What are the exclusions?"
196
+
197
+ **Coverage Mode**
198
+ - "What is covered?"
199
+ - "What does this policy include?"
200
+
201
+ **Terms Explanation**
202
+ - "Explain IDV"
203
+ - "What does NCB mean?"
204
+
205
+ **General Mode**
206
+ - Everything else
207
+ """)
208
+
209
+ st.markdown("---")
210
+
211
+ # Clear chat button
212
+ if st.button("Clear Chat History", use_container_width=True):
213
+ st.session_state.messages = []
214
+ st.rerun()
215
+
216
+
217
+ def main():
218
+ """Main application"""
219
+ # Initialize session state
220
+ initialize_session_state()
221
+
222
+ # Render sidebar
223
+ sidebar()
224
+
225
+ # Main header
226
+ st.markdown('<div class="main-header">Insurance Helper</div>', unsafe_allow_html=True)
227
+ st.markdown('<div class="sub-header">Understanding your insurance made easy</div>', unsafe_allow_html=True)
228
+
229
+ # Display chat messages
230
+ for message in st.session_state.messages:
231
+ display_message(message)
232
+
233
+ # Chat input
234
+ if prompt := st.chat_input("Ask about your insurance..."):
235
+ # Add user message
236
+ st.session_state.messages.append({"role": "user", "content": prompt})
237
+ display_message({"role": "user", "content": prompt})
238
+
239
+ # Get response based on auto-detected query intent
240
+ with st.chat_message("assistant"):
241
+ with st.spinner("Thinking..."):
242
+ try:
243
+ # Automatically detect query intent
244
+ detected_intent = detect_query_intent(prompt)
245
+
246
+ intent_name = {
247
+ "addons": "Add-ons Analysis",
248
+ "exclusions": "Exclusions Check",
249
+ "coverage": "Coverage Analysis",
250
+ "terms": "Term Explanation",
251
+ "general": "General Query"
252
+ }
253
+
254
+ st.caption(f"{intent_name.get(detected_intent, 'General Query')}")
255
+
256
+ # Route to appropriate query method
257
+ if detected_intent == "addons":
258
+ result = st.session_state.rag_chain.query_specific_section(
259
+ prompt,
260
+ section_type="addons"
261
+ )
262
+ elif detected_intent == "exclusions":
263
+ result = st.session_state.rag_chain.query_specific_section(
264
+ prompt,
265
+ section_type="exclusions"
266
+ )
267
+ elif detected_intent == "coverage":
268
+ result = st.session_state.rag_chain.query_specific_section(
269
+ prompt,
270
+ section_type="coverage"
271
+ )
272
+ else: # terms or general
273
+ result = st.session_state.rag_chain.query(prompt)
274
+
275
+ # Display answer
276
+ st.markdown(result["answer"])
277
+
278
+ # Display sources
279
+ if "sources" in result and result["sources"]:
280
+ with st.expander(f"View {len(result['sources'])} Sources"):
281
+ for source in result["sources"]:
282
+ st.markdown(f"""
283
+ <div class="source-box">
284
+ <strong>Source {source['index']}:</strong> {source['source_file']} (Page {source['page']})<br>
285
+ <strong>Section:</strong> {source['section_type']}<br>
286
+ <strong>Preview:</strong> {source['content_preview']}
287
+ </div>
288
+ """, unsafe_allow_html=True)
289
+
290
+ # Add assistant message to history
291
+ st.session_state.messages.append({
292
+ "role": "assistant",
293
+ "content": result["answer"],
294
+ "sources": result.get("sources", [])
295
+ })
296
+
297
+ except Exception as e:
298
+ error_msg = f"Error processing query: {str(e)}"
299
+ st.error(error_msg)
300
+ st.session_state.messages.append({
301
+ "role": "assistant",
302
+ "content": error_msg
303
+ })
304
+
305
+
306
+ if __name__ == "__main__":
307
+ try:
308
+ Config.validate_config()
309
+ main()
310
+ except ValueError as e:
311
+ st.error(f"Configuration Error: {str(e)}")
312
+ st.info("Please ensure your .env file contains all required API keys.")