rairo commited on
Commit
9e58256
Β·
verified Β·
1 Parent(s): 3dfbdac

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +149 -142
app.py CHANGED
@@ -13,7 +13,6 @@ from langchain.text_splitter import CharacterTextSplitter
13
  from langchain.chains import ConversationalRetrievalChain
14
  from langchain.memory import ConversationBufferMemory
15
  import urllib.parse
16
- import plotly.express as px
17
 
18
  # Ensure Playwright installs required browsers and dependencies
19
  subprocess.run(["playwright", "install"])
@@ -28,6 +27,7 @@ graph_config = {
28
  },
29
  }
30
 
 
31
  def get_data(url):
32
  smart_scraper_graph = SmartScraperGraph(
33
  prompt=(
@@ -40,14 +40,16 @@ def get_data(url):
40
  )
41
  return smart_scraper_graph.run()
42
 
 
43
  def process_multiple_urls(urls):
44
  """
45
- Process multiple URLs with progress tracking.
46
  """
47
  all_data = {"grants": []}
48
  progress_bar = st.progress(0)
49
  status_container = st.empty()
50
  total_urls = len(urls)
 
51
  for index, url in enumerate(urls):
52
  try:
53
  url = url.strip()
@@ -58,26 +60,31 @@ def process_multiple_urls(urls):
58
  progress_bar.progress(progress)
59
  status_container.markdown(
60
  f"""
61
- **Processing URL {index+1} of {total_urls}**
62
- πŸ” Scanning: `{url}`
63
- βœ… Completed: {index}/{total_urls}
64
- ⏳ Remaining: {total_urls - index - 1}
65
- """
 
66
  )
 
67
  result = get_data(url)
68
  if result and "grants" in result:
69
  all_data["grants"].extend(result["grants"])
70
  except Exception as e:
71
- st.error(f"Error processing {url}: {str(e)}")
72
  continue
 
73
  progress_bar.empty()
74
  status_container.empty()
75
  return all_data
76
 
 
77
  def convert_to_csv(data):
78
  df = pd.DataFrame(data["grants"])
79
  return df.to_csv(index=False).encode("utf-8")
80
 
 
81
  def convert_to_excel(data):
82
  df = pd.DataFrame(data["grants"])
83
  buffer = io.BytesIO()
@@ -85,170 +92,170 @@ def convert_to_excel(data):
85
  df.to_excel(writer, sheet_name="Grants", index=False)
86
  return buffer.getvalue()
87
 
 
88
  def create_knowledge_base(data):
89
  documents = []
90
  for grant in data["grants"]:
91
  doc_parts = [f"{key.replace('_', ' ').title()}: {value}" for key, value in grant.items()]
92
  documents.append("\n".join(doc_parts))
 
93
  text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
94
  texts = text_splitter.create_documents(documents)
 
95
  embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=GOOGLE_API_KEY)
96
  vectorstore = FAISS.from_documents(texts, embeddings)
 
97
  llm = ChatGoogleGenerativeAI(
98
  model="gemini-2.0-flash-thinking-exp", google_api_key=GOOGLE_API_KEY, temperature=0
99
  )
100
  memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
101
  return ConversationalRetrievalChain.from_llm(llm, vectorstore.as_retriever(), memory=memory)
102
 
 
103
  def get_shareable_link(file_data, file_name, file_type):
104
  b64 = base64.b64encode(file_data).decode()
105
  return f"data:{file_type};base64,{b64}"
106
 
107
- def display_dashboard(data):
108
- df = pd.DataFrame(data["grants"])
109
- if df.empty:
110
- st.info("No data available for dashboard.")
111
- return
112
- st.subheader("Grants Dashboard")
113
- # Filtering by sector if available
114
- if "sector" in df.columns:
115
- sectors = df["sector"].dropna().unique().tolist()
116
- selected_sector = st.selectbox("Select Sector", options=["All"] + sectors)
117
- if selected_sector != "All":
118
- df = df[df["sector"] == selected_sector]
119
- # Visualizations: Distribution of Grant Values
120
- if "value" in df.columns:
121
- df["value"] = pd.to_numeric(df["value"], errors="coerce")
122
- fig_value = px.histogram(df, x="value", nbins=20, title="Distribution of Grant Values")
123
- st.plotly_chart(fig_value)
124
- # Visualization: Grants by Organisation
125
- if "organisation" in df.columns:
126
- fig_org = px.pie(df, names="organisation", title="Grants by Organisation")
127
- st.plotly_chart(fig_org)
128
- st.dataframe(df)
129
-
130
- def display_scrape_tab():
131
- st.header("Scrape Grants")
132
- url_input = st.text_area(
133
- "Enter URLs (one per line)",
 
 
 
 
 
 
 
 
 
 
134
  height=150,
135
- help="Enter multiple URLs separated by new lines",
 
136
  )
137
- if st.button("Start Scraping"):
 
 
138
  if url_input:
139
  urls = [url.strip() for url in url_input.split("\n") if url.strip()]
140
  if urls:
141
- with st.spinner("Scraping grants..."):
142
- result = process_multiple_urls(urls)
143
- st.session_state.scraped_data = result
144
- st.success(f"Scraped {len(result['grants'])} grants from {len(urls)} URL(s)!")
 
 
 
145
  else:
146
- st.warning("Please enter valid URLs.")
147
- else:
148
- st.warning("Please enter at least one URL.")
149
-
150
- def display_download_tab():
151
- st.header("Download & Explore Data")
152
- if st.session_state.get("scraped_data"):
153
- result = st.session_state.scraped_data
154
- df = pd.DataFrame(result["grants"])
155
- st.subheader(f"Data Preview ({len(df)} grants)")
156
- st.dataframe(df)
157
- selected_format = st.selectbox("Select Download Format", ("CSV", "Excel"))
158
- if selected_format == "CSV":
159
- file_data = convert_to_csv(result)
160
- file_name = "grants.csv"
161
- file_type = "text/csv"
162
  else:
163
- file_data = convert_to_excel(result)
164
- file_name = "grants.xlsx"
165
- file_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
166
- b64 = base64.b64encode(file_data).decode()
167
- download_link = f"<a href='data:{file_type};base64,{b64}' download='{file_name}'>Download {selected_format}</a>"
168
- st.markdown(download_link, unsafe_allow_html=True)
169
- shareable_link = get_shareable_link(file_data, file_name, file_type)
170
- st.markdown("---")
171
- st.markdown("**Share Options:**")
172
- whatsapp_url = f"https://api.whatsapp.com/send?text={urllib.parse.quote(f'Check out this file: {shareable_link}')}"
173
- st.markdown(f"πŸ“± [Share via WhatsApp]({whatsapp_url})")
174
- email_subject = urllib.parse.quote("Check out this grants file")
175
- email_body = urllib.parse.quote(f"Download the file here: {shareable_link}")
176
- email_url = f"mailto:?subject={email_subject}&body={email_body}"
177
- st.markdown(f"πŸ“§ [Share via Email]({email_url})")
178
- else:
179
- st.info("No scraped data available. Please scrape grants first.")
180
-
181
- def display_chat_tab():
182
- st.header("Knowledge Base Chat")
183
- st.info("Ask questions about the grants data.")
184
- if st.session_state.get("scraped_data"):
185
- if st.button("Load Data as Knowledge Base"):
186
- st.session_state.qa_chain = create_knowledge_base(st.session_state.scraped_data)
187
- st.session_state.chat_interface_active = True
188
- st.session_state.chat_history = []
189
- st.success("Knowledge base loaded!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  if st.session_state.get("chat_interface_active"):
191
- query = st.text_input("Enter your query:")
 
 
 
 
192
  if query:
193
- response = st.session_state.qa_chain({"question": query})
194
- st.session_state.chat_history.append({"query": query, "response": response["answer"]})
195
- for chat in st.session_state.get("chat_history", []):
196
- st.markdown(f"**You:** {chat['query']}")
197
- st.markdown(f"**Grants Bot:** {chat['response']}")
198
- else:
199
- st.info("Load the knowledge base to start chatting.")
 
 
 
 
 
 
 
200
  else:
201
- st.info("No scraped data available. Please scrape grants first.")
202
-
203
- def display_alerts_tab():
204
- st.header("Automated Alerts Setup")
205
- st.write("Configure your personalized alerts for new grant opportunities.")
206
- keyword = st.text_input("Keyword for Alerts", help="E.g., 'AI', 'sustainable research'")
207
- sector = st.text_input("Sector", help="E.g., 'health', 'technology'")
208
- email = st.text_input("Your Email", help="Enter your email to receive alerts")
209
- if st.button("Save Alert Preferences"):
210
- # Placeholder for saving alert preferencesβ€”integrate with an email service in a full implementation.
211
- st.success("Alert preferences saved! You will be notified when matching grants are found.")
212
- st.info("Note: Automated alert functionality is under development and will be integrated soon.")
213
 
214
- def main():
215
- st.set_page_config(page_title="Quantilytix Grants Platform", layout="wide", initial_sidebar_state="expanded")
216
- # Custom CSS styling for a modern look
217
- st.markdown("""
218
- <style>
219
- .main {
220
- background-color: #f5f5f5;
221
- }
222
- .sidebar .sidebar-content {
223
- background-image: linear-gradient(#2e7bcf, #2e7bcf);
224
- color: white;
225
- }
226
- </style>
227
- """, unsafe_allow_html=True)
228
-
229
- st.sidebar.title("Quantilytix Grants Platform")
230
- st.sidebar.image("logoqb.jpeg", use_column_width=True)
231
- app_mode = st.sidebar.radio("Navigation", ["Scrape Grants", "Download & Explore", "Dashboard", "Knowledge Base Chat", "Automated Alerts"])
232
-
233
- if app_mode == "Scrape Grants":
234
- display_scrape_tab()
235
- elif app_mode == "Download & Explore":
236
- display_download_tab()
237
- elif app_mode == "Dashboard":
238
- if st.session_state.get("scraped_data"):
239
- display_dashboard(st.session_state.scraped_data)
240
- else:
241
- st.info("No data available. Please scrape grants first.")
242
- elif app_mode == "Knowledge Base Chat":
243
- display_chat_tab()
244
- elif app_mode == "Automated Alerts":
245
- display_alerts_tab()
246
 
247
  if __name__ == "__main__":
248
- if "scraped_data" not in st.session_state:
249
- st.session_state.scraped_data = None
250
- if "chat_history" not in st.session_state:
251
- st.session_state.chat_history = []
252
- if "chat_interface_active" not in st.session_state:
253
- st.session_state.chat_interface_active = False
254
  main()
 
13
  from langchain.chains import ConversationalRetrievalChain
14
  from langchain.memory import ConversationBufferMemory
15
  import urllib.parse
 
16
 
17
  # Ensure Playwright installs required browsers and dependencies
18
  subprocess.run(["playwright", "install"])
 
27
  },
28
  }
29
 
30
+
31
  def get_data(url):
32
  smart_scraper_graph = SmartScraperGraph(
33
  prompt=(
 
40
  )
41
  return smart_scraper_graph.run()
42
 
43
+
44
  def process_multiple_urls(urls):
45
  """
46
+ Process multiple URLs with enhanced progress tracking and user feedback.
47
  """
48
  all_data = {"grants": []}
49
  progress_bar = st.progress(0)
50
  status_container = st.empty()
51
  total_urls = len(urls)
52
+
53
  for index, url in enumerate(urls):
54
  try:
55
  url = url.strip()
 
60
  progress_bar.progress(progress)
61
  status_container.markdown(
62
  f"""
63
+ **Processing Grant Opportunities** πŸš€
64
+ Scanning URL {index+1} of {total_urls}: `{url}`
65
+ <br>
66
+ <p style='font-size: 0.9em; color: #6699CC;'>Completed: {index}/{total_urls} | Remaining: {total_urls - index - 1}</p>
67
+ """,
68
+ unsafe_allow_html=True,
69
  )
70
+
71
  result = get_data(url)
72
  if result and "grants" in result:
73
  all_data["grants"].extend(result["grants"])
74
  except Exception as e:
75
+ st.error(f"⚠️ Error processing URL: {url} - {str(e)}")
76
  continue
77
+
78
  progress_bar.empty()
79
  status_container.empty()
80
  return all_data
81
 
82
+
83
  def convert_to_csv(data):
84
  df = pd.DataFrame(data["grants"])
85
  return df.to_csv(index=False).encode("utf-8")
86
 
87
+
88
  def convert_to_excel(data):
89
  df = pd.DataFrame(data["grants"])
90
  buffer = io.BytesIO()
 
92
  df.to_excel(writer, sheet_name="Grants", index=False)
93
  return buffer.getvalue()
94
 
95
+
96
  def create_knowledge_base(data):
97
  documents = []
98
  for grant in data["grants"]:
99
  doc_parts = [f"{key.replace('_', ' ').title()}: {value}" for key, value in grant.items()]
100
  documents.append("\n".join(doc_parts))
101
+
102
  text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
103
  texts = text_splitter.create_documents(documents)
104
+
105
  embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001", google_api_key=GOOGLE_API_KEY)
106
  vectorstore = FAISS.from_documents(texts, embeddings)
107
+
108
  llm = ChatGoogleGenerativeAI(
109
  model="gemini-2.0-flash-thinking-exp", google_api_key=GOOGLE_API_KEY, temperature=0
110
  )
111
  memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
112
  return ConversationalRetrievalChain.from_llm(llm, vectorstore.as_retriever(), memory=memory)
113
 
114
+
115
  def get_shareable_link(file_data, file_name, file_type):
116
  b64 = base64.b64encode(file_data).decode()
117
  return f"data:{file_type};base64,{b64}"
118
 
119
+
120
+ def main():
121
+ st.set_page_config(page_title="Quantilytix Grant Finder", page_icon="πŸ’°", layout="wide")
122
+ st.title("πŸ’° Quantilytix Grant Finder")
123
+
124
+ # --- Introduction and Motivation ---
125
+ st.markdown("""
126
+ <div style="text-align: justify;">
127
+ <p>
128
+ Welcome to <b>Quantilytix Grant Finder</b>, an AI-powered platform designed to streamline the grant discovery process, especially for academics and researchers in Zimbabwe and LMICs.
129
+ In the evolving landscape of development, securing research funding is crucial, yet often challenging. Our platform leverages advanced AI to make grant searching more efficient and accessible.
130
+ </p>
131
+ <p>
132
+ <b>Inspired by the Quintuple Helix Model and the pressing need for research funding in LMICs</b>, this tool aims to bridge the gap between researchers with innovative ideas and funding opportunities worldwide.
133
+ We understand the scarcity of research funds, particularly in regions like Zimbabwe, and are committed to providing a cost-effective, tailored solution.
134
+ </p>
135
+ <p>
136
+ <b>Future enhancements</b> will include personalized alerts and customized access models to cater to both individual academics and research offices, ensuring that relevant grant opportunities are never missed.
137
+ Stay tuned for pricing assessments tailored for universities and research institutions in Zimbabwe, making advanced grant searching accessible to all.
138
+ </p>
139
+ </div>
140
+ """, unsafe_allow_html=True)
141
+
142
+ st.sidebar.image("logoqb.jpeg", use_container_width=True)
143
+ st.sidebar.header("Scrape & Configure")
144
+
145
+ # Initialize session state
146
+ if "scraped_data" not in st.session_state:
147
+ st.session_state.scraped_data = None
148
+ if "chat_history" not in st.session_state:
149
+ st.session_state.chat_history = []
150
+ if "chat_interface_active" not in st.session_state:
151
+ st.session_state.chat_interface_active = False
152
+
153
+ # URL Input in Sidebar
154
+ url_input = st.sidebar.text_area(
155
+ "Enter Grant URLs (one per line)",
156
  height=150,
157
+ help="Input URLs from funding websites. Add each URL on a new line.",
158
+ placeholder="e.g.,\nhttps://www.example-grants.org/opportunities\nhttps://another-funding-source.com/grants-list"
159
  )
160
+
161
+ # Get Grants Button with Icon
162
+ if st.sidebar.button("πŸ” Get Grant Opportunities"):
163
  if url_input:
164
  urls = [url.strip() for url in url_input.split("\n") if url.strip()]
165
  if urls:
166
+ try:
167
+ with st.spinner("Scraping in progress... Please wait patiently."):
168
+ result = process_multiple_urls(urls)
169
+ st.session_state.scraped_data = result
170
+ st.success(f"βœ… Successfully scraped {len(result['grants'])} grant opportunities from {len(urls)} URLs!")
171
+ except Exception as e:
172
+ st.error(f"🚨 Scraping process encountered an error: {e}")
173
  else:
174
+ st.warning("⚠️ Please enter valid URLs.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  else:
176
+ st.warning("⚠️ Please enter at least one URL to begin scraping.")
177
+
178
+ # --- Main Panel for Data Display and Chat ---
179
+ st.markdown("---")
180
+
181
+ if st.session_state.scraped_data and st.session_state.scraped_data['grants']:
182
+ st.header("πŸ“Š Scraped Grant Data")
183
+
184
+ # Data Preview and Download Options in Main Panel
185
+ with st.expander(f"πŸ“Š Preview Grant Data ({len(st.session_state.scraped_data['grants']} grants)"):
186
+ st.dataframe(st.session_state.scraped_data["grants"])
187
+
188
+ col1, col2, col3 = st.columns([1, 1, 2]) # Adjust column widths for better layout
189
+
190
+ with col1:
191
+ selected_format = st.selectbox("Download As:", ("CSV", "Excel"), key="download_format_selector")
192
+
193
+ with col2:
194
+ if selected_format == "CSV":
195
+ file_data = convert_to_csv(st.session_state.scraped_data)
196
+ file_name = "grants_data.csv"
197
+ file_type = "text/csv"
198
+ else:
199
+ file_data = convert_to_excel(st.session_state.scraped_data)
200
+ file_name = "grants_data.xlsx"
201
+ file_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
202
+
203
+ download_link_html = f"<a href='data:{file_type};base64,{base64.b64encode(file_data).decode()}' download='{file_name}'><button style='background-color:#4CAF50;color:white;padding:10px 15px;border:none;border-radius:4px;'>⬇️ Download {selected_format}</button></a>"
204
+ st.markdown(download_link_html, unsafe_allow_html=True)
205
+
206
+ with col3:
207
+ shareable_link = get_shareable_link(file_data, file_name, file_type)
208
+ whatsapp_url = f"https://api.whatsapp.com/send?text={urllib.parse.quote(f'Check out these grant opportunities: {shareable_link}')}"
209
+ email_subject = urllib.parse.quote("Grant Opportunities File")
210
+ email_body = urllib.parse.quote(f"Download the grant opportunities file here: {shareable_link}")
211
+ email_url = f"mailto:?subject={email_subject}&body={email_body}"
212
+
213
+ st.markdown("<div style='margin-top:10px;'>Share via:</div>", unsafe_allow_html=True) # Add some margin for better spacing
214
+ st.markdown(f"πŸ“± [WhatsApp]({whatsapp_url}) | πŸ“§ [Email]({email_url})", unsafe_allow_html=True)
215
+
216
+
217
+ # Knowledge Base and Chat Interface
218
+ if st.button("🧠 Load as Knowledge Base & Chat"):
219
+ with st.spinner("Loading data into knowledge base..."):
220
+ st.session_state.qa_chain = create_knowledge_base(st.session_state.scraped_data)
221
+ st.session_state.chat_interface_active = True
222
+ st.session_state.chat_history = [] # Clear chat history on reload
223
+ st.success("Knowledge base loaded! You can now chat with the Grants Bot.")
224
+
225
  if st.session_state.get("chat_interface_active"):
226
+ st.markdown("---")
227
+ st.header("πŸ’¬ Chat with Grants Bot")
228
+ st.markdown("Ask questions about the scraped grants to get quick insights!")
229
+
230
+ query = st.text_input("Your question:", key="chat_input")
231
  if query:
232
+ if st.session_state.qa_chain:
233
+ with st.spinner("Generating response..."):
234
+ response = st.session_state.qa_chain({"question": query})
235
+ st.session_state.chat_history.append({"query": query, "response": response["answer"]})
236
+ else:
237
+ st.error("Knowledge base not initialized. Please load data as knowledge base.")
238
+
239
+ if st.session_state.chat_history:
240
+ st.subheader("Chat History")
241
+ for chat in st.session_state.chat_history:
242
+ st.markdown(f"<div style='padding: 10px; border-radius: 5px; margin-bottom: 5px; background-color: #f0f2f6;'><strong>You:</strong> {chat['query']}</div>", unsafe_allow_html=True)
243
+ st.markdown(f"<div style='padding: 10px; border-radius: 5px; margin-bottom: 10px; background-color: #e0e2e6;'><strong>Grants Bot:</strong> {chat['response']}</div>", unsafe_allow_html=True)
244
+
245
+
246
  else:
247
+ st.info("⬅️ Enter URLs in the sidebar and click 'Get Grant Opportunities' to start scraping.")
248
+
249
+ st.sidebar.markdown("---")
250
+ st.sidebar.markdown(
251
+ """
252
+ <div style='text-align: center; font-size: 0.8em; color: grey;'>
253
+ Powered by <a href="https://quantilytix.com" style='color: grey;'>Quantilytix</a> | &copy; 2025
254
+ </div>
255
+ """,
256
+ unsafe_allow_html=True,
257
+ )
 
258
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
  if __name__ == "__main__":
 
 
 
 
 
 
261
  main()