Abs6187 commited on
Commit
b97233e
Β·
verified Β·
1 Parent(s): 4d32b7c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +562 -455
app.py CHANGED
@@ -1,456 +1,563 @@
1
- import streamlit as st
2
- from llama_index.core.agent import ReActAgent
3
- from llama_index.llms.groq import Groq
4
- from llama_index.core.tools import FunctionTool
5
- from llama_index.tools.tavily_research.base import TavilyToolSpec
6
- import os
7
- import json
8
- import pandas as pd
9
- from datetime import datetime
10
- from dotenv import load_dotenv
11
- import time
12
- import base64
13
- import plotly.graph_objects as go
14
- import re
15
-
16
- # Load environment variables
17
- load_dotenv()
18
-
19
- # Initialize session state if not already done
20
- if 'conversation_history' not in st.session_state:
21
- st.session_state.conversation_history = []
22
- if 'api_key' not in st.session_state:
23
- st.session_state.api_key = ""
24
- if 'current_response' not in st.session_state:
25
- st.session_state.current_response = None
26
- if 'feedback_data' not in st.session_state:
27
- st.session_state.feedback_data = []
28
- if 'current_sources' not in st.session_state:
29
- st.session_state.current_sources = []
30
-
31
- # Custom CSS for better UI
32
- st.markdown("""
33
- <style>
34
- .main-header {
35
- font-size: 2.5rem;
36
- color: #4527A0;
37
- text-align: center;
38
- margin-bottom: 1rem;
39
- font-weight: bold;
40
- }
41
- .sub-header {
42
- font-size: 1.5rem;
43
- color: #5E35B1;
44
- margin-bottom: 0.5rem;
45
- }
46
- .team-header {
47
- font-size: 1.2rem;
48
- color: #673AB7;
49
- font-weight: bold;
50
- margin-top: 1rem;
51
- }
52
- .team-member {
53
- font-size: 1rem;
54
- margin-left: 1rem;
55
- color: #7E57C2;
56
- }
57
- .api-section {
58
- background-color: #EDE7F6;
59
- padding: 1rem;
60
- border-radius: 10px;
61
- margin-bottom: 1rem;
62
- }
63
- .response-container {
64
- background-color: #F3E5F5;
65
- padding: 1rem;
66
- border-radius: 5px;
67
- margin-top: 1rem;
68
- }
69
- .footer {
70
- text-align: center;
71
- margin-top: 2rem;
72
- font-size: 0.8rem;
73
- color: #9575CD;
74
- }
75
- .error-msg {
76
- color: #D32F2F;
77
- font-weight: bold;
78
- }
79
- .success-msg {
80
- color: #388E3C;
81
- font-weight: bold;
82
- }
83
- .history-item {
84
- padding: 0.5rem;
85
- border-radius: 5px;
86
- margin-bottom: 0.5rem;
87
- }
88
- .query-text {
89
- font-weight: bold;
90
- color: #303F9F;
91
- }
92
- .response-text {
93
- color: #1A237E;
94
- }
95
- .feedback-container {
96
- background-color: #E8EAF6;
97
- padding: 1rem;
98
- border-radius: 5px;
99
- margin-top: 1rem;
100
- }
101
- .feedback-btn {
102
- margin-right: 0.5rem;
103
- }
104
- .star-rating {
105
- display: flex;
106
- justify-content: center;
107
- margin-top: 0.5rem;
108
- }
109
- .analytics-container {
110
- background-color: #E1F5FE;
111
- padding: 1rem;
112
- border-radius: 5px;
113
- margin-top: 1rem;
114
- }
115
- .sources-container {
116
- background-color: #E0F7FA;
117
- padding: 1rem;
118
- border-radius: 5px;
119
- margin-top: 1rem;
120
- }
121
- .source-item {
122
- background-color: #B2EBF2;
123
- padding: 0.5rem;
124
- border-radius: 5px;
125
- margin-bottom: 0.5rem;
126
- }
127
- .source-url {
128
- font-style: italic;
129
- color: #0277BD;
130
- word-break: break-all;
131
- }
132
- </style>
133
- """, unsafe_allow_html=True)
134
-
135
- # Main title and description
136
- st.markdown('<div class="main-header">TechMatrix AI Web Search Agent</div>', unsafe_allow_html=True)
137
- st.markdown('''
138
- This intelligent agent uses state-of-the-art LLM technology to search the web and provide comprehensive answers to your questions.
139
- Simply enter your query, and let our AI handle the rest!
140
- ''')
141
-
142
- # Sidebar for team information
143
- with st.sidebar:
144
- st.markdown('<div class="team-header">TechMatrix Solvers</div>', unsafe_allow_html=True)
145
-
146
- st.markdown('<div class="team-member">πŸ‘‘ Abhay Gupta (Team Leader)</div>', unsafe_allow_html=True)
147
- st.markdown('[LinkedIn Profile](https://www.linkedin.com/in/abhay-gupta-197b17264/)')
148
-
149
- st.markdown('<div class="team-member">🧠 Mayank Das Bairagi</div>', unsafe_allow_html=True)
150
- st.markdown('[LinkedIn Profile](https://www.linkedin.com/in/mayank-das-bairagi-18639525a/)')
151
-
152
- st.markdown('<div class="team-member">πŸ’» Kripanshu Gupta</div>', unsafe_allow_html=True)
153
- st.markdown('[LinkedIn Profile](https://www.linkedin.com/in/kripanshu-gupta-a66349261/)')
154
-
155
- st.markdown('<div class="team-member">πŸ” Bhumika Patel</div>', unsafe_allow_html=True)
156
- st.markdown('[LinkedIn Profile](https://www.linkedin.com/in/bhumika-patel-ml/)')
157
-
158
- st.markdown('---')
159
-
160
- # Advanced Settings
161
- st.markdown('<div class="sub-header">Advanced Settings</div>', unsafe_allow_html=True)
162
- model_option = st.selectbox(
163
- 'LLM Model',
164
- ('gemma2-9b-it', 'llama3-8b-8192', 'mixtral-8x7b-32768'),
165
- index=0
166
- )
167
-
168
- search_depth = st.slider('Search Depth', min_value=1, max_value=5, value=3,
169
- help="Higher values will search more thoroughly but take longer")
170
-
171
- # Clear history button
172
- if st.button('Clear Conversation History'):
173
- st.session_state.conversation_history = []
174
- st.success('Conversation history cleared!')
175
-
176
- # Analytics section in sidebar
177
- if st.session_state.feedback_data:
178
- st.markdown('---')
179
- st.markdown('<div class="sub-header">Response Analytics</div>', unsafe_allow_html=True)
180
-
181
- # Calculate average rating
182
- ratings = [item['rating'] for item in st.session_state.feedback_data if 'rating' in item]
183
- avg_rating = sum(ratings) / len(ratings) if ratings else 0
184
-
185
- # Create a chart
186
- fig = go.Figure(go.Indicator(
187
- mode="gauge+number",
188
- value=avg_rating,
189
- title={'text': "Average Rating"},
190
- domain={'x': [0, 1], 'y': [0, 1]},
191
- gauge={
192
- 'axis': {'range': [0, 5]},
193
- 'bar': {'color': "#6200EA"},
194
- 'steps': [
195
- {'range': [0, 2], 'color': "#FFD0D0"},
196
- {'range': [2, 3.5], 'color': "#FFFFCC"},
197
- {'range': [3.5, 5], 'color': "#D0FFD0"}
198
- ]
199
- }
200
- ))
201
-
202
- fig.update_layout(height=250, margin=dict(l=20, r=20, t=30, b=20))
203
- st.plotly_chart(fig, use_container_width=True)
204
-
205
- # Show feedback counts
206
- feedback_counts = {"πŸ‘ Helpful": 0, "πŸ‘Ž Not Helpful": 0}
207
- for item in st.session_state.feedback_data:
208
- if 'feedback' in item:
209
- if item['feedback'] == 'helpful':
210
- feedback_counts["πŸ‘ Helpful"] += 1
211
- elif item['feedback'] == 'not_helpful':
212
- feedback_counts["πŸ‘Ž Not Helpful"] += 1
213
-
214
- st.markdown("### Feedback Summary")
215
- for key, value in feedback_counts.items():
216
- st.markdown(f"**{key}:** {value}")
217
-
218
- # API key input section
219
- st.markdown('<div class="sub-header">API Credentials</div>', unsafe_allow_html=True)
220
- with st.expander("Configure API Keys"):
221
- st.markdown('<div class="api-section">', unsafe_allow_html=True)
222
- api_key = st.text_input("Enter your Groq API key:",
223
- type="password",
224
- value=st.session_state.api_key,
225
- help="Get your API key from https://console.groq.com/keys")
226
-
227
- tavily_key = st.text_input("Enter your Tavily API key (optional):",
228
- type="password",
229
- help="Get your Tavily API key from https://tavily.com/#api")
230
-
231
- if api_key:
232
- st.session_state.api_key = api_key
233
- os.environ['GROQ_API_KEY'] = api_key
234
-
235
- if tavily_key:
236
- os.environ['TAVILY_API_KEY'] = tavily_key
237
- st.markdown('</div>', unsafe_allow_html=True)
238
-
239
- # Function to create download link for text data
240
- def get_download_link(text, filename, link_text):
241
- b64 = base64.b64encode(text.encode()).decode()
242
- href = f'<a href="data:file/txt;base64,{b64}" download="{filename}">{link_text}</a>'
243
- return href
244
-
245
- # Function to handle feedback submission
246
- def submit_feedback(feedback_type, query, response):
247
- feedback_entry = {
248
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
249
- "query": query,
250
- "response": response,
251
- "feedback": feedback_type
252
- }
253
- st.session_state.feedback_data.append(feedback_entry)
254
- return True
255
-
256
- # Function to submit rating
257
- def submit_rating(rating, query, response):
258
- # Find if there's an existing entry for this query/response
259
- for entry in st.session_state.feedback_data:
260
- if entry.get('query') == query and entry.get('response') == response:
261
- entry['rating'] = rating
262
- return True
263
-
264
- # If not found, create a new entry
265
- feedback_entry = {
266
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
267
- "query": query,
268
- "response": response,
269
- "rating": rating
270
- }
271
- st.session_state.feedback_data.append(feedback_entry)
272
- return True
273
-
274
- # Function to extract URLs from text
275
- def extract_urls(text):
276
- url_pattern = r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+'
277
- return re.findall(url_pattern, text)
278
-
279
- # Setup search tools
280
- try:
281
- if 'TAVILY_API_KEY' in os.environ and os.environ['TAVILY_API_KEY']:
282
- search = TavilyToolSpec(api_key=os.environ['TAVILY_API_KEY'])
283
- else:
284
- # Fallback to a default key or inform the user
285
- st.warning("Using default Tavily API key with limited quota. For better results, please provide your own key.")
286
- search = TavilyToolSpec(api_key=os.getenv('TAVILY_API_KEY'))
287
-
288
- def search_tool(prompt: str) -> list:
289
- """Search the web for information about the given prompt."""
290
- try:
291
- search_results = search.search(prompt, max_results=search_depth)
292
- # Store source URLs
293
- sources = []
294
- for result in search_results:
295
- if hasattr(result, 'url') and result.url:
296
- sources.append({
297
- 'title': result.title if hasattr(result, 'title') else "Unknown Source",
298
- 'url': result.url
299
- })
300
-
301
- # Store in session state for later display
302
- st.session_state.current_sources = sources
303
-
304
- return [result.text for result in search_results]
305
- except Exception as e:
306
- return [f"Error during search: {str(e)}"]
307
-
308
- search_toolkit = FunctionTool.from_defaults(fn=search_tool)
309
- except Exception as e:
310
- st.error(f"Error setting up search tools: {str(e)}")
311
- search_toolkit = None
312
-
313
- # Query input
314
- query = st.text_input("What would you like to know?",
315
- placeholder="Enter your question here...",
316
- help="Ask any question, and our AI will search the web for answers")
317
-
318
- # Search button
319
- search_button = st.button("πŸ” Search")
320
-
321
- # Process the search when button is clicked
322
- if search_button and query:
323
- # Check if API key is provided
324
- if not st.session_state.api_key:
325
- st.error("Please enter your Groq API key first!")
326
- else:
327
- try:
328
- with st.spinner("🧠 Searching the web and analyzing results..."):
329
- # Initialize the LLM and agent
330
- llm = Groq(model=model_option)
331
- agent = ReActAgent.from_tools([search_toolkit], llm=llm, verbose=True)
332
-
333
- # Clear current sources before the new search
334
- st.session_state.current_sources = []
335
-
336
- # Get the response
337
- start_time = time.time()
338
- response = agent.chat(query)
339
- end_time = time.time()
340
-
341
- # Extract any additional URLs from the response
342
- additional_urls = extract_urls(response.response)
343
- for url in additional_urls:
344
- if not any(source['url'] == url for source in st.session_state.current_sources):
345
- st.session_state.current_sources.append({
346
- 'title': "Referenced Source",
347
- 'url': url
348
- })
349
-
350
- # Store the response in session state
351
- st.session_state.current_response = {
352
- "query": query,
353
- "response": response.response,
354
- "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
355
- "duration": round(end_time - start_time, 2),
356
- "sources": st.session_state.current_sources
357
- }
358
-
359
- # Add to conversation history
360
- st.session_state.conversation_history.append(st.session_state.current_response)
361
-
362
- # Display success message
363
- st.success(f"Found results in {round(end_time - start_time, 2)} seconds!")
364
- except Exception as e:
365
- st.error(f"An error occurred: {str(e)}")
366
-
367
- # Display current response if available
368
- if st.session_state.current_response:
369
- with st.container():
370
- st.markdown('<div class="response-container">', unsafe_allow_html=True)
371
- st.markdown("### Response:")
372
- st.write(st.session_state.current_response["response"])
373
-
374
- # Export options
375
- col1, col2 = st.columns(2)
376
- with col1:
377
- st.markdown(
378
- get_download_link(
379
- st.session_state.current_response["response"],
380
- f"search_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
381
- "Download as Text"
382
- ),
383
- unsafe_allow_html=True
384
- )
385
- with col2:
386
- # Create JSON with metadata
387
- json_data = json.dumps({
388
- "query": st.session_state.current_response["query"],
389
- "response": st.session_state.current_response["response"],
390
- "timestamp": st.session_state.current_response["time"],
391
- "processing_time": st.session_state.current_response["duration"],
392
- "sources": st.session_state.current_sources if "sources" in st.session_state.current_response else []
393
- }, indent=4)
394
-
395
- st.markdown(
396
- get_download_link(
397
- json_data,
398
- f"search_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
399
- "Download as JSON"
400
- ),
401
- unsafe_allow_html=True
402
- )
403
- st.markdown('</div>', unsafe_allow_html=True)
404
-
405
- # Display sources if available
406
- if "sources" in st.session_state.current_response and st.session_state.current_response["sources"]:
407
- with st.expander("View Sources", expanded=True):
408
- st.markdown('<div class="sources-container">', unsafe_allow_html=True)
409
- for i, source in enumerate(st.session_state.current_response["sources"]):
410
- st.markdown(f'<div class="source-item">', unsafe_allow_html=True)
411
- st.markdown(f"**Source {i+1}:** {source.get('title', 'Unknown Source')}")
412
- st.markdown(f'<div class="source-url"><a href="{source["url"]}" target="_blank">{source["url"]}</a></div>', unsafe_allow_html=True)
413
- st.markdown('</div>', unsafe_allow_html=True)
414
- st.markdown('</div>', unsafe_allow_html=True)
415
-
416
- # Feedback section
417
- st.markdown('<div class="feedback-container">', unsafe_allow_html=True)
418
- st.markdown("### Was this response helpful?")
419
-
420
- col1, col2 = st.columns(2)
421
- with col1:
422
- if st.button("πŸ‘ Helpful", key="helpful_btn"):
423
- if submit_feedback("helpful", st.session_state.current_response["query"], st.session_state.current_response["response"]):
424
- st.success("Thank you for your feedback!")
425
- with col2:
426
- if st.button("πŸ‘Ž Not Helpful", key="not_helpful_btn"):
427
- if submit_feedback("not_helpful", st.session_state.current_response["query"], st.session_state.current_response["response"]):
428
- st.success("Thank you for your feedback! We'll work to improve our responses.")
429
-
430
- st.markdown("### Rate this response:")
431
- rating = st.slider("", min_value=1, max_value=5, value=4,
432
- help="Rate the quality of this response from 1 (poor) to 5 (excellent)")
433
-
434
- if st.button("Submit Rating"):
435
- if submit_rating(rating, st.session_state.current_response["query"], st.session_state.current_response["response"]):
436
- st.success("Rating submitted! Thank you for helping us improve.")
437
-
438
- st.markdown('</div>', unsafe_allow_html=True)
439
-
440
- # Display conversation history
441
- if st.session_state.conversation_history:
442
- with st.expander("View Conversation History"):
443
- for i, item in enumerate(reversed(st.session_state.conversation_history)):
444
- st.markdown(f'<div class="history-item">', unsafe_allow_html=True)
445
- st.markdown(f'<span class="query-text">Q: {item["query"]}</span> <small>({item["time"]})</small>', unsafe_allow_html=True)
446
- st.markdown(f'<div class="response-text">A: {item["response"][:200]}{"..." if len(item["response"]) > 200 else ""}</div>', unsafe_allow_html=True)
447
- st.markdown('</div>', unsafe_allow_html=True)
448
- if i < len(st.session_state.conversation_history) - 1:
449
- st.markdown('---')
450
-
451
- # Footer with attribution
452
- st.markdown('''
453
- <div class="footer">
454
- <p>Powered by Groq + Llama-Index + Tavily Search | Created by TechMatrix Solvers | 2024</p>
455
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  ''', unsafe_allow_html=True)
 
1
+ import streamlit as st
2
+ from llama_index.core.agent import ReActAgent
3
+ from llama_index.llms.groq import Groq
4
+ from llama_index.core.tools import FunctionTool
5
+ from llama_index.tools.tavily_research.base import TavilyToolSpec
6
+ import os
7
+ import json
8
+ import pandas as pd
9
+ from datetime import datetime
10
+ from dotenv import load_dotenv
11
+ import time
12
+ import base64
13
+ import plotly.graph_objects as go
14
+ import re
15
+ from io import StringIO
16
+ import sys
17
+
18
+ # Load environment variables
19
+ load_dotenv()
20
+
21
+ # Initialize session state if not already done
22
+ if 'conversation_history' not in st.session_state:
23
+ st.session_state.conversation_history = []
24
+ if 'api_key' not in st.session_state:
25
+ st.session_state.api_key = ""
26
+ if 'current_response' not in st.session_state:
27
+ st.session_state.current_response = None
28
+ if 'feedback_data' not in st.session_state:
29
+ st.session_state.feedback_data = []
30
+ if 'current_sources' not in st.session_state:
31
+ st.session_state.current_sources = []
32
+ if 'thinking_process' not in st.session_state:
33
+ st.session_state.thinking_process = ""
34
+
35
+ # Custom CSS for better UI
36
+ st.markdown("""
37
+ <style>
38
+ .main-header {
39
+ font-size: 2.5rem;
40
+ color: #4527A0;
41
+ text-align: center;
42
+ margin-bottom: 1rem;
43
+ font-weight: bold;
44
+ }
45
+ .sub-header {
46
+ font-size: 1.5rem;
47
+ color: #5E35B1;
48
+ margin-bottom: 0.5rem;
49
+ }
50
+ .team-header {
51
+ font-size: 1.2rem;
52
+ color: #673AB7;
53
+ font-weight: bold;
54
+ margin-top: 1rem;
55
+ }
56
+ .team-member {
57
+ font-size: 1rem;
58
+ margin-left: 1rem;
59
+ color: #7E57C2;
60
+ }
61
+ .api-section {
62
+ background-color: #EDE7F6;
63
+ padding: 1rem;
64
+ border-radius: 10px;
65
+ margin-bottom: 1rem;
66
+ }
67
+ .response-container {
68
+ background-color: #F3E5F5;
69
+ padding: 1rem;
70
+ border-radius: 5px;
71
+ margin-top: 1rem;
72
+ }
73
+ .footer {
74
+ text-align: center;
75
+ margin-top: 2rem;
76
+ font-size: 0.8rem;
77
+ color: #9575CD;
78
+ }
79
+ .error-msg {
80
+ color: #D32F2F;
81
+ font-weight: bold;
82
+ }
83
+ .success-msg {
84
+ color: #388E3C;
85
+ font-weight: bold;
86
+ }
87
+ .history-item {
88
+ padding: 0.5rem;
89
+ border-radius: 5px;
90
+ margin-bottom: 0.5rem;
91
+ }
92
+ .query-text {
93
+ font-weight: bold;
94
+ color: #303F9F;
95
+ }
96
+ .response-text {
97
+ color: #1A237E;
98
+ }
99
+ .feedback-container {
100
+ background-color: #E8EAF6;
101
+ padding: 1rem;
102
+ border-radius: 5px;
103
+ margin-top: 1rem;
104
+ }
105
+ .feedback-btn {
106
+ margin-right: 0.5rem;
107
+ }
108
+ .star-rating {
109
+ display: flex;
110
+ justify-content: center;
111
+ margin-top: 0.5rem;
112
+ }
113
+ .analytics-container {
114
+ background-color: #E1F5FE;
115
+ padding: 1rem;
116
+ border-radius: 5px;
117
+ margin-top: 1rem;
118
+ }
119
+ .sources-container {
120
+ background-color: #E0F7FA;
121
+ padding: 1rem;
122
+ border-radius: 5px;
123
+ margin-top: 1rem;
124
+ }
125
+ .source-item {
126
+ background-color: #B2EBF2;
127
+ padding: 0.5rem;
128
+ border-radius: 5px;
129
+ margin-bottom: 0.5rem;
130
+ }
131
+ .source-url {
132
+ font-style: italic;
133
+ color: #0277BD;
134
+ word-break: break-all;
135
+ }
136
+ .thinking-container {
137
+ background-color: #FFF8E1;
138
+ padding: 1rem;
139
+ border-radius: 5px;
140
+ margin-top: 1rem;
141
+ font-family: monospace;
142
+ white-space: pre-wrap;
143
+ }
144
+ .thinking-step {
145
+ padding: 0.5rem;
146
+ margin-bottom: 0.5rem;
147
+ border-left: 3px solid #FFB300;
148
+ }
149
+ .website-link {
150
+ display: inline-block;
151
+ margin: 0.3rem;
152
+ padding: 0.4rem 0.8rem;
153
+ background-color: #E3F2FD;
154
+ color: #1565C0;
155
+ border-radius: 20px;
156
+ font-size: 0.9rem;
157
+ text-decoration: none;
158
+ transition: background-color 0.3s;
159
+ }
160
+ .website-link:hover {
161
+ background-color: #BBDEFB;
162
+ }
163
+ .link-container {
164
+ margin: 1rem 0;
165
+ padding: 0.5rem;
166
+ background-color: #F5F5F5;
167
+ border-radius: 5px;
168
+ display: flex;
169
+ flex-wrap: wrap;
170
+ }
171
+ </style>
172
+ """, unsafe_allow_html=True)
173
+
174
+ # Main title and description
175
+ st.markdown('<div class="main-header">TechMatrix AI Web Search Agent</div>', unsafe_allow_html=True)
176
+ st.markdown('''
177
+ This intelligent agent uses state-of-the-art LLM technology to search the web and provide comprehensive answers to your questions.
178
+ Simply enter your query, and let our AI handle the rest!
179
+ ''')
180
+
181
+ # Sidebar for team information
182
+ with st.sidebar:
183
+ st.markdown('<div class="team-header">TechMatrix Solvers</div>', unsafe_allow_html=True)
184
+
185
+ st.markdown('<div class="team-member">πŸ‘‘ Abhay Gupta (Team Leader)</div>', unsafe_allow_html=True)
186
+ st.markdown('[LinkedIn Profile](https://www.linkedin.com/in/abhay-gupta-197b17264/)')
187
+
188
+ st.markdown('<div class="team-member">🧠 Mayank Das Bairagi</div>', unsafe_allow_html=True)
189
+ st.markdown('[LinkedIn Profile](https://www.linkedin.com/in/mayank-das-bairagi-18639525a/)')
190
+
191
+ st.markdown('<div class="team-member">πŸ’» Kripanshu Gupta</div>', unsafe_allow_html=True)
192
+ st.markdown('[LinkedIn Profile](https://www.linkedin.com/in/kripanshu-gupta-a66349261/)')
193
+
194
+ st.markdown('<div class="team-member">πŸ” Bhumika Patel</div>', unsafe_allow_html=True)
195
+ st.markdown('[LinkedIn Profile](https://www.linkedin.com/in/bhumika-patel-ml/)')
196
+
197
+ st.markdown('---')
198
+
199
+ # Advanced Settings
200
+ st.markdown('<div class="sub-header">Advanced Settings</div>', unsafe_allow_html=True)
201
+ model_option = st.selectbox(
202
+ 'LLM Model',
203
+ ('gemma2-9b-it', 'llama3-8b-8192', 'mixtral-8x7b-32768', 'llama3-70b-8192', 'claude-3-5-sonnet-20240620'),
204
+ index=0,
205
+ help="Select from available Groq models - all free with no rate limits!"
206
+ )
207
+
208
+ search_depth = st.slider('Search Depth', min_value=1, max_value=8, value=5,
209
+ help="Higher values will search more thoroughly but take longer")
210
+
211
+ show_thinking = st.checkbox('Show AI Thinking Process', value=True,
212
+ help="Display the step-by-step reasoning process of the AI")
213
+
214
+ # Clear history button
215
+ if st.button('Clear Conversation History'):
216
+ st.session_state.conversation_history = []
217
+ st.success('Conversation history cleared!')
218
+
219
+ # Analytics section in sidebar
220
+ if st.session_state.feedback_data:
221
+ st.markdown('---')
222
+ st.markdown('<div class="sub-header">Response Analytics</div>', unsafe_allow_html=True)
223
+
224
+ # Calculate average rating
225
+ ratings = [item['rating'] for item in st.session_state.feedback_data if 'rating' in item]
226
+ avg_rating = sum(ratings) / len(ratings) if ratings else 0
227
+
228
+ # Create a chart
229
+ fig = go.Figure(go.Indicator(
230
+ mode="gauge+number",
231
+ value=avg_rating,
232
+ title={'text': "Average Rating"},
233
+ domain={'x': [0, 1], 'y': [0, 1]},
234
+ gauge={
235
+ 'axis': {'range': [0, 5]},
236
+ 'bar': {'color': "#6200EA"},
237
+ 'steps': [
238
+ {'range': [0, 2], 'color': "#FFD0D0"},
239
+ {'range': [2, 3.5], 'color': "#FFFFCC"},
240
+ {'range': [3.5, 5], 'color': "#D0FFD0"}
241
+ ]
242
+ }
243
+ ))
244
+
245
+ fig.update_layout(height=250, margin=dict(l=20, r=20, t=30, b=20))
246
+ st.plotly_chart(fig, use_container_width=True)
247
+
248
+ # Show feedback counts
249
+ feedback_counts = {"πŸ‘ Helpful": 0, "πŸ‘Ž Not Helpful": 0}
250
+ for item in st.session_state.feedback_data:
251
+ if 'feedback' in item:
252
+ if item['feedback'] == 'helpful':
253
+ feedback_counts["πŸ‘ Helpful"] += 1
254
+ elif item['feedback'] == 'not_helpful':
255
+ feedback_counts["πŸ‘Ž Not Helpful"] += 1
256
+
257
+ st.markdown("### Feedback Summary")
258
+ for key, value in feedback_counts.items():
259
+ st.markdown(f"**{key}:** {value}")
260
+
261
+ # API key input section
262
+ st.markdown('<div class="sub-header">API Credentials</div>', unsafe_allow_html=True)
263
+ with st.expander("Configure API Keys"):
264
+ st.markdown('<div class="api-section">', unsafe_allow_html=True)
265
+ api_key = st.text_input("Enter your Groq API key:",
266
+ type="password",
267
+ value=st.session_state.api_key,
268
+ help="Get your API key from https://console.groq.com/keys")
269
+
270
+ tavily_key = st.text_input("Enter your Tavily API key (optional):",
271
+ type="password",
272
+ help="Get your Tavily API key from https://tavily.com/#api")
273
+
274
+ if api_key:
275
+ st.session_state.api_key = api_key
276
+ os.environ['GROQ_API_KEY'] = api_key
277
+
278
+ if tavily_key:
279
+ os.environ['TAVILY_API_KEY'] = tavily_key
280
+ st.markdown('</div>', unsafe_allow_html=True)
281
+
282
+ # Function to create download link for text data
283
+ def get_download_link(text, filename, link_text):
284
+ b64 = base64.b64encode(text.encode()).decode()
285
+ href = f'<a href="data:file/txt;base64,{b64}" download="{filename}">{link_text}</a>'
286
+ return href
287
+
288
+ # Function to handle feedback submission
289
+ def submit_feedback(feedback_type, query, response):
290
+ feedback_entry = {
291
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
292
+ "query": query,
293
+ "response": response,
294
+ "feedback": feedback_type
295
+ }
296
+ st.session_state.feedback_data.append(feedback_entry)
297
+ return True
298
+
299
+ # Function to submit rating
300
+ def submit_rating(rating, query, response):
301
+ # Find if there's an existing entry for this query/response
302
+ for entry in st.session_state.feedback_data:
303
+ if entry.get('query') == query and entry.get('response') == response:
304
+ entry['rating'] = rating
305
+ return True
306
+
307
+ # If not found, create a new entry
308
+ feedback_entry = {
309
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
310
+ "query": query,
311
+ "response": response,
312
+ "rating": rating
313
+ }
314
+ st.session_state.feedback_data.append(feedback_entry)
315
+ return True
316
+
317
+ # Function to extract URLs from text
318
+ def extract_urls(text):
319
+ url_pattern = r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+'
320
+ return re.findall(url_pattern, text)
321
+
322
+ # Custom callback to capture agent's thinking process
323
+ class ThinkingCapture:
324
+ def __init__(self):
325
+ self.thinking_steps = []
326
+
327
+ def on_agent_step(self, agent_step):
328
+ # Capture the thought process
329
+ if hasattr(agent_step, 'thought') and agent_step.thought:
330
+ self.thinking_steps.append(f"Thought: {agent_step.thought}")
331
+ if hasattr(agent_step, 'action') and agent_step.action:
332
+ self.thinking_steps.append(f"Action: {agent_step.action}")
333
+ if hasattr(agent_step, 'observation') and agent_step.observation:
334
+ self.thinking_steps.append(f"Observation: {agent_step.observation}")
335
+ return agent_step
336
+
337
+ def get_thinking_process(self):
338
+ return "\n".join(self.thinking_steps)
339
+
340
+ # Setup search tools
341
+ try:
342
+ if 'TAVILY_API_KEY' in os.environ and os.environ['TAVILY_API_KEY']:
343
+ search = TavilyToolSpec(api_key=os.environ['TAVILY_API_KEY'])
344
+ else:
345
+ # Fallback to a default key or inform the user
346
+ st.warning("Using default Tavily API key with limited quota. For better results, please provide your own key.")
347
+ search = TavilyToolSpec(api_key=os.getenv('TAVILY_API_KEY'))
348
+
349
+ def search_tool(prompt: str) -> list:
350
+ """Search the web for information about the given prompt."""
351
+ try:
352
+ search_results = search.search(prompt, max_results=search_depth)
353
+ # Store source URLs
354
+ sources = []
355
+ for result in search_results:
356
+ if hasattr(result, 'url') and result.url:
357
+ sources.append({
358
+ 'title': result.title if hasattr(result, 'title') else "Unknown Source",
359
+ 'url': result.url
360
+ })
361
+
362
+ # Store in session state for later display
363
+ st.session_state.current_sources = sources
364
+
365
+ return [result.text for result in search_results]
366
+ except Exception as e:
367
+ return [f"Error during search: {str(e)}"]
368
+
369
+ search_toolkit = FunctionTool.from_defaults(fn=search_tool)
370
+ except Exception as e:
371
+ st.error(f"Error setting up search tools: {str(e)}")
372
+ search_toolkit = None
373
+
374
+ # Query input
375
+ query = st.text_input("What would you like to know?",
376
+ placeholder="Enter your question here...",
377
+ help="Ask any question, and our AI will search the web for answers")
378
+
379
+ # Search button
380
+ search_button = st.button("πŸ” Search")
381
+
382
+ # Process the search when button is clicked
383
+ if search_button and query:
384
+ # Check if API key is provided
385
+ if not st.session_state.api_key:
386
+ st.error("Please enter your Groq API key first!")
387
+ else:
388
+ try:
389
+ with st.spinner("🧠 Searching the web and analyzing results..."):
390
+ # Initialize the LLM and agent
391
+ llm = Groq(model=model_option)
392
+
393
+ # Initialize the thinking capture
394
+ thinking_capture = ThinkingCapture()
395
+
396
+ # Create the agent with step callbacks
397
+ agent = ReActAgent.from_tools(
398
+ [search_toolkit],
399
+ llm=llm,
400
+ verbose=True,
401
+ step_callbacks=[thinking_capture.on_agent_step]
402
+ )
403
+
404
+ # Clear current sources before the new search
405
+ st.session_state.current_sources = []
406
+
407
+ # Get the response
408
+ start_time = time.time()
409
+ response = agent.chat(query)
410
+ end_time = time.time()
411
+
412
+ # Store the thinking process
413
+ st.session_state.thinking_process = thinking_capture.get_thinking_process()
414
+
415
+ # Extract any additional URLs from the response
416
+ additional_urls = extract_urls(response.response)
417
+ for url in additional_urls:
418
+ if not any(source['url'] == url for source in st.session_state.current_sources):
419
+ st.session_state.current_sources.append({
420
+ 'title': "Referenced Source",
421
+ 'url': url
422
+ })
423
+
424
+ # Store the response in session state
425
+ st.session_state.current_response = {
426
+ "query": query,
427
+ "response": response.response,
428
+ "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
429
+ "duration": round(end_time - start_time, 2),
430
+ "sources": st.session_state.current_sources,
431
+ "thinking": st.session_state.thinking_process
432
+ }
433
+
434
+ # Add to conversation history
435
+ st.session_state.conversation_history.append(st.session_state.current_response)
436
+
437
+ # Display success message
438
+ st.success(f"Found results in {round(end_time - start_time, 2)} seconds!")
439
+ except Exception as e:
440
+ st.error(f"An error occurred: {str(e)}")
441
+
442
+ # Display quick source links if available
443
+ if st.session_state.current_sources:
444
+ st.markdown("### Source Websites:")
445
+ st.markdown('<div class="link-container">', unsafe_allow_html=True)
446
+ for i, source in enumerate(st.session_state.current_sources[:5]): # Show top 5 sources
447
+ st.markdown(f'<a class="website-link" href="{source["url"]}" target="_blank">πŸ“„ {source.get("title", "Source "+str(i+1))[:30]}...</a>', unsafe_allow_html=True)
448
+ st.markdown('</div>', unsafe_allow_html=True)
449
+
450
+ # Display current response if available
451
+ if st.session_state.current_response:
452
+ with st.container():
453
+ st.markdown('<div class="response-container">', unsafe_allow_html=True)
454
+ st.markdown("### Response:")
455
+ st.write(st.session_state.current_response["response"])
456
+
457
+ # Export options
458
+ col1, col2 = st.columns(2)
459
+ with col1:
460
+ st.markdown(
461
+ get_download_link(
462
+ st.session_state.current_response["response"],
463
+ f"search_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
464
+ "Download as Text"
465
+ ),
466
+ unsafe_allow_html=True
467
+ )
468
+ with col2:
469
+ # Create JSON with metadata
470
+ json_data = json.dumps({
471
+ "query": st.session_state.current_response["query"],
472
+ "response": st.session_state.current_response["response"],
473
+ "timestamp": st.session_state.current_response["time"],
474
+ "processing_time": st.session_state.current_response["duration"],
475
+ "sources": st.session_state.current_sources if "sources" in st.session_state.current_response else [],
476
+ "thinking_process": st.session_state.thinking_process if "thinking" in st.session_state.current_response else ""
477
+ }, indent=4)
478
+
479
+ st.markdown(
480
+ get_download_link(
481
+ json_data,
482
+ f"search_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
483
+ "Download as JSON with Sources"
484
+ ),
485
+ unsafe_allow_html=True
486
+ )
487
+ st.markdown('</div>', unsafe_allow_html=True)
488
+
489
+ # Display thinking process if enabled
490
+ if show_thinking and "thinking" in st.session_state.current_response:
491
+ with st.expander("View AI Thinking Process", expanded=True):
492
+ st.markdown('<div class="thinking-container">', unsafe_allow_html=True)
493
+
494
+ # Create a formatted display of the thinking steps
495
+ thinking_text = st.session_state.current_response["thinking"]
496
+ steps = thinking_text.split('\n')
497
+
498
+ for step in steps:
499
+ if step.strip():
500
+ step_type = ""
501
+ if step.startswith("Thought:"):
502
+ step_type = "πŸ’­"
503
+ elif step.startswith("Action:"):
504
+ step_type = "πŸ”"
505
+ elif step.startswith("Observation:"):
506
+ step_type = "πŸ“Š"
507
+
508
+ st.markdown(f'<div class="thinking-step">{step_type} {step}</div>', unsafe_allow_html=True)
509
+
510
+ st.markdown('</div>', unsafe_allow_html=True)
511
+
512
+ # Display sources if available
513
+ if "sources" in st.session_state.current_response and st.session_state.current_response["sources"]:
514
+ with st.expander("View Detailed Sources", expanded=True):
515
+ st.markdown('<div class="sources-container">', unsafe_allow_html=True)
516
+ for i, source in enumerate(st.session_state.current_response["sources"]):
517
+ st.markdown(f'<div class="source-item">', unsafe_allow_html=True)
518
+ st.markdown(f"**Source {i+1}:** {source.get('title', 'Unknown Source')}")
519
+ st.markdown(f'<div class="source-url"><a href="{source["url"]}" target="_blank">{source["url"]}</a></div>', unsafe_allow_html=True)
520
+ st.markdown('</div>', unsafe_allow_html=True)
521
+ st.markdown('</div>', unsafe_allow_html=True)
522
+
523
+ # Feedback section
524
+ st.markdown('<div class="feedback-container">', unsafe_allow_html=True)
525
+ st.markdown("### Was this response helpful?")
526
+
527
+ col1, col2 = st.columns(2)
528
+ with col1:
529
+ if st.button("πŸ‘ Helpful", key="helpful_btn"):
530
+ if submit_feedback("helpful", st.session_state.current_response["query"], st.session_state.current_response["response"]):
531
+ st.success("Thank you for your feedback!")
532
+ with col2:
533
+ if st.button("πŸ‘Ž Not Helpful", key="not_helpful_btn"):
534
+ if submit_feedback("not_helpful", st.session_state.current_response["query"], st.session_state.current_response["response"]):
535
+ st.success("Thank you for your feedback! We'll work to improve our responses.")
536
+
537
+ st.markdown("### Rate this response:")
538
+ rating = st.slider("", min_value=1, max_value=5, value=4,
539
+ help="Rate the quality of this response from 1 (poor) to 5 (excellent)")
540
+
541
+ if st.button("Submit Rating"):
542
+ if submit_rating(rating, st.session_state.current_response["query"], st.session_state.current_response["response"]):
543
+ st.success("Rating submitted! Thank you for helping us improve.")
544
+
545
+ st.markdown('</div>', unsafe_allow_html=True)
546
+
547
+ # Display conversation history
548
+ if st.session_state.conversation_history:
549
+ with st.expander("View Conversation History"):
550
+ for i, item in enumerate(reversed(st.session_state.conversation_history)):
551
+ st.markdown(f'<div class="history-item">', unsafe_allow_html=True)
552
+ st.markdown(f'<span class="query-text">Q: {item["query"]}</span> <small>({item["time"]})</small>', unsafe_allow_html=True)
553
+ st.markdown(f'<div class="response-text">A: {item["response"][:200]}{"..." if len(item["response"]) > 200 else ""}</div>', unsafe_allow_html=True)
554
+ st.markdown('</div>', unsafe_allow_html=True)
555
+ if i < len(st.session_state.conversation_history) - 1:
556
+ st.markdown('---')
557
+
558
+ # Footer with attribution
559
+ st.markdown('''
560
+ <div class="footer">
561
+ <p>Powered by Groq + Llama-Index + Tavily Search | Created by TechMatrix Solvers | 2025</p>
562
+ </div>
563
  ''', unsafe_allow_html=True)