Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import os
|
3 |
+
import re
|
4 |
+
from datetime import datetime
|
5 |
+
import json
|
6 |
+
from openai import AzureOpenAI
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
|
9 |
+
# Load environment variables
|
10 |
+
load_dotenv()
|
11 |
+
|
12 |
+
|
13 |
+
# For Hugging Face Spaces
|
14 |
+
if 'AZURE_OPENAI_API_KEY' in os.environ:
|
15 |
+
api_key = os.environ['AZURE_OPENAI_API_KEY']
|
16 |
+
endpoint = os.environ['AZURE_OPENAI_ENDPOINT']
|
17 |
+
else:
|
18 |
+
# Fall back to dotenv for local development
|
19 |
+
from dotenv import load_dotenv
|
20 |
+
load_dotenv()
|
21 |
+
api_key = os.getenv("AZURE_OPENAI_API_KEY")
|
22 |
+
endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
|
23 |
+
|
24 |
+
# Configure Azure OpenAI client
|
25 |
+
client = AzureOpenAI(
|
26 |
+
api_key=api_key,
|
27 |
+
api_version="2024-02-15-preview",
|
28 |
+
azure_endpoint=endpoint
|
29 |
+
)
|
30 |
+
|
31 |
+
# Set page configuration
|
32 |
+
st.set_page_config(
|
33 |
+
page_title="CSR Initiative Portal",
|
34 |
+
page_icon="🌱",
|
35 |
+
layout="wide"
|
36 |
+
)
|
37 |
+
|
38 |
+
# CSS to improve UI
|
39 |
+
st.markdown("""
|
40 |
+
<style>
|
41 |
+
.main {
|
42 |
+
padding: 2rem;
|
43 |
+
}
|
44 |
+
.stButton>button {
|
45 |
+
width: 100%;
|
46 |
+
margin-top: 10px;
|
47 |
+
}
|
48 |
+
.title {
|
49 |
+
text-align: center;
|
50 |
+
margin-bottom: 30px;
|
51 |
+
}
|
52 |
+
.form-section {
|
53 |
+
background-color: #f8f9fa;
|
54 |
+
padding: 20px;
|
55 |
+
border-radius: 10px;
|
56 |
+
margin-bottom: 20px;
|
57 |
+
}
|
58 |
+
</style>
|
59 |
+
""", unsafe_allow_html=True)
|
60 |
+
|
61 |
+
# Title
|
62 |
+
st.markdown("<h1 class='title'>CSR Initiative Portal</h1>", unsafe_allow_html=True)
|
63 |
+
|
64 |
+
# List of beneficiary groups
|
65 |
+
beneficiary_groups = [
|
66 |
+
"Supporting next generation",
|
67 |
+
"Impact entrepreneurs",
|
68 |
+
"Environment sustainability",
|
69 |
+
"Disaster response"
|
70 |
+
]
|
71 |
+
|
72 |
+
def extract_json_from_text(text):
|
73 |
+
"""
|
74 |
+
Extracts JSON from text that might contain other content.
|
75 |
+
"""
|
76 |
+
# Try to find JSON pattern with regex first
|
77 |
+
json_pattern = r'({[\s\S]*})'
|
78 |
+
matches = re.search(json_pattern, text)
|
79 |
+
|
80 |
+
if matches:
|
81 |
+
json_text = matches.group(1)
|
82 |
+
try:
|
83 |
+
return json.loads(json_text)
|
84 |
+
except:
|
85 |
+
pass
|
86 |
+
|
87 |
+
# If regex extraction fails, try cleaning the text directly
|
88 |
+
text = text.strip()
|
89 |
+
|
90 |
+
# If the text starts with markdown code block, remove it
|
91 |
+
if text.startswith('```json'):
|
92 |
+
text = text[7:]
|
93 |
+
elif text.startswith('```'):
|
94 |
+
text = text[3:]
|
95 |
+
|
96 |
+
# If the text ends with markdown code block, remove it
|
97 |
+
if text.endswith('```'):
|
98 |
+
text = text[:-3]
|
99 |
+
|
100 |
+
# Try to find the start of the JSON object if there's other text before it
|
101 |
+
if not text.startswith('{'):
|
102 |
+
try:
|
103 |
+
start_idx = text.index('{')
|
104 |
+
text = text[start_idx:]
|
105 |
+
except ValueError:
|
106 |
+
return None
|
107 |
+
|
108 |
+
# Try to find the end of the JSON object if there's other text after it
|
109 |
+
if not text.endswith('}'):
|
110 |
+
try:
|
111 |
+
end_idx = text.rindex('}')
|
112 |
+
text = text[:end_idx+1]
|
113 |
+
except ValueError:
|
114 |
+
return None
|
115 |
+
|
116 |
+
# Try to parse the cleaned text
|
117 |
+
try:
|
118 |
+
return json.loads(text)
|
119 |
+
except json.JSONDecodeError as e:
|
120 |
+
st.error(f"JSON parsing error: {str(e)}")
|
121 |
+
return None
|
122 |
+
|
123 |
+
def analyze_initiative_with_ai(description):
|
124 |
+
"""
|
125 |
+
Use Azure OpenAI to analyze initiative description and extract fields
|
126 |
+
"""
|
127 |
+
system_prompt = """
|
128 |
+
You are an AI assistant for a CSR portal. Analyze the initiative description and extract the following fields accurately:
|
129 |
+
|
130 |
+
1. Initiative Name (create a concise, descriptive name)
|
131 |
+
2. Date (extract start and end dates)
|
132 |
+
3. Location (extract location)
|
133 |
+
4. Who will be working with and who will the initiative help? (2-line description)
|
134 |
+
5. What will the participant be doing? (2-line description)
|
135 |
+
6. How does the initiative align with focus areas and how will it change lives? (2-line description)
|
136 |
+
7. Role Name (extract or suggest an appropriate role)
|
137 |
+
8. Role Description (2-line description)
|
138 |
+
9. Start date (in format YYYY-MM-DD)
|
139 |
+
10. End date (in format YYYY-MM-DD)
|
140 |
+
11. Deadline date (in format YYYY-MM-DD)
|
141 |
+
12. Beneficiary group (select ONE from: Supporting next generation, Impact entrepreneurs, Environment sustainability, Disaster response)
|
142 |
+
|
143 |
+
Format your response as JSON with exact keys corresponding to the field names above.
|
144 |
+
IMPORTANT: Provide ONLY the JSON object with no additional text before or after.
|
145 |
+
"""
|
146 |
+
|
147 |
+
try:
|
148 |
+
# Using the new OpenAI API format
|
149 |
+
response = client.chat.completions.create(
|
150 |
+
model="gpt-4o-mini", # Adjust to your deployed model name
|
151 |
+
messages=[
|
152 |
+
{"role": "system", "content": system_prompt},
|
153 |
+
{"role": "user", "content": description}
|
154 |
+
],
|
155 |
+
temperature=0.3,
|
156 |
+
max_tokens=1000,
|
157 |
+
response_format={"type": "json_object"} # Request JSON formatted response
|
158 |
+
)
|
159 |
+
|
160 |
+
# Extract the generated text
|
161 |
+
result = response.choices[0].message.content
|
162 |
+
|
163 |
+
# Parse the JSON with our robust function
|
164 |
+
parsed_result = extract_json_from_text(result)
|
165 |
+
|
166 |
+
if parsed_result:
|
167 |
+
return parsed_result
|
168 |
+
else:
|
169 |
+
st.error("Failed to parse AI response into a valid JSON format.")
|
170 |
+
return None
|
171 |
+
except Exception as e:
|
172 |
+
st.error(f"Error analyzing initiative: {str(e)}")
|
173 |
+
return None
|
174 |
+
|
175 |
+
# Initialize session state for form fields if they don't exist
|
176 |
+
if 'initiative_name' not in st.session_state:
|
177 |
+
st.session_state.initiative_name = ""
|
178 |
+
if 'date' not in st.session_state:
|
179 |
+
st.session_state.date = ""
|
180 |
+
if 'location' not in st.session_state:
|
181 |
+
st.session_state.location = ""
|
182 |
+
if 'owner' not in st.session_state:
|
183 |
+
st.session_state.owner = "Prabaharan S"
|
184 |
+
if 'working_with' not in st.session_state:
|
185 |
+
st.session_state.working_with = ""
|
186 |
+
if 'participant_doing' not in st.session_state:
|
187 |
+
st.session_state.participant_doing = ""
|
188 |
+
if 'alignment' not in st.session_state:
|
189 |
+
st.session_state.alignment = ""
|
190 |
+
if 'role_name' not in st.session_state:
|
191 |
+
st.session_state.role_name = ""
|
192 |
+
if 'role_description' not in st.session_state:
|
193 |
+
st.session_state.role_description = ""
|
194 |
+
if 'start_date' not in st.session_state:
|
195 |
+
st.session_state.start_date = None
|
196 |
+
if 'end_date' not in st.session_state:
|
197 |
+
st.session_state.end_date = None
|
198 |
+
if 'deadline_date' not in st.session_state:
|
199 |
+
st.session_state.deadline_date = None
|
200 |
+
if 'beneficiary_group' not in st.session_state:
|
201 |
+
st.session_state.beneficiary_group = beneficiary_groups[0]
|
202 |
+
|
203 |
+
# Create two columns for layout
|
204 |
+
col1, col2 = st.columns([1, 1])
|
205 |
+
|
206 |
+
with col1:
|
207 |
+
st.markdown("<div class='form-section'>", unsafe_allow_html=True)
|
208 |
+
st.subheader("Create with AI")
|
209 |
+
|
210 |
+
# Text area for the initiative description
|
211 |
+
initiative_description = st.text_area(
|
212 |
+
"Enter a detailed description of your initiative",
|
213 |
+
height=300,
|
214 |
+
placeholder="Describe your initiative in detail. Include information about dates, location, activities, beneficiaries, and goals."
|
215 |
+
)
|
216 |
+
|
217 |
+
# Button to trigger AI analysis
|
218 |
+
if st.button("Generate with AI", key="generate_button"):
|
219 |
+
if initiative_description:
|
220 |
+
with st.spinner("Analyzing your initiative..."):
|
221 |
+
result = analyze_initiative_with_ai(initiative_description)
|
222 |
+
|
223 |
+
if result:
|
224 |
+
# For debugging: Show raw JSON
|
225 |
+
st.write("Debug - Generated JSON:", result)
|
226 |
+
|
227 |
+
# Update the session state with AI-generated content
|
228 |
+
st.session_state.initiative_name = result.get("Initiative Name", "")
|
229 |
+
st.session_state.date = result.get("Date", "")
|
230 |
+
st.session_state.location = result.get("Location", "")
|
231 |
+
st.session_state.working_with = result.get("Who will be working with and who will the initiative help?", "")
|
232 |
+
st.session_state.participant_doing = result.get("What will the participant be doing?", "")
|
233 |
+
st.session_state.alignment = result.get("How does the initiative align with focus areas and how will it change lives?", "")
|
234 |
+
st.session_state.role_name = result.get("Role Name", "")
|
235 |
+
st.session_state.role_description = result.get("Role Description", "")
|
236 |
+
|
237 |
+
# Handle dates
|
238 |
+
try:
|
239 |
+
start_date_str = result.get("Start date", "")
|
240 |
+
if start_date_str:
|
241 |
+
st.session_state.start_date = datetime.strptime(start_date_str, "%Y-%m-%d").date()
|
242 |
+
except Exception as e:
|
243 |
+
st.warning(f"Could not parse start date: {start_date_str}. Error: {str(e)}")
|
244 |
+
|
245 |
+
try:
|
246 |
+
end_date_str = result.get("End date", "")
|
247 |
+
if end_date_str:
|
248 |
+
st.session_state.end_date = datetime.strptime(end_date_str, "%Y-%m-%d").date()
|
249 |
+
except Exception as e:
|
250 |
+
st.warning(f"Could not parse end date: {end_date_str}. Error: {str(e)}")
|
251 |
+
|
252 |
+
try:
|
253 |
+
deadline_date_str = result.get("Deadline date", "")
|
254 |
+
if deadline_date_str:
|
255 |
+
st.session_state.deadline_date = datetime.strptime(deadline_date_str, "%Y-%m-%d").date()
|
256 |
+
except Exception as e:
|
257 |
+
st.warning(f"Could not parse deadline date: {deadline_date_str}. Error: {str(e)}")
|
258 |
+
|
259 |
+
# Handle beneficiary group
|
260 |
+
predicted_beneficiary = result.get("Beneficiary group", "")
|
261 |
+
if predicted_beneficiary in beneficiary_groups:
|
262 |
+
st.session_state.beneficiary_group = predicted_beneficiary
|
263 |
+
|
264 |
+
# Display success message
|
265 |
+
st.success("Initiative details generated successfully!")
|
266 |
+
st.rerun() # Rerun the app to update the form fields
|
267 |
+
else:
|
268 |
+
st.warning("Please enter a description of your initiative first.")
|
269 |
+
|
270 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
271 |
+
|
272 |
+
with col2:
|
273 |
+
st.markdown("<div class='form-section'>", unsafe_allow_html=True)
|
274 |
+
st.subheader("Initiative Details")
|
275 |
+
|
276 |
+
# Form for user to input initiative details manually
|
277 |
+
initiative_name = st.text_input("Initiative Name", value=st.session_state.initiative_name)
|
278 |
+
date = st.text_input("Date", value=st.session_state.date)
|
279 |
+
location = st.text_input("Location", value=st.session_state.location)
|
280 |
+
owner = st.text_input("Owner", value=st.session_state.owner)
|
281 |
+
|
282 |
+
working_with = st.text_area("Who will be working with and who will the initiative help?",
|
283 |
+
value=st.session_state.working_with, height=100)
|
284 |
+
participant_doing = st.text_area("What will the participant be doing?",
|
285 |
+
value=st.session_state.participant_doing, height=100)
|
286 |
+
alignment = st.text_area("How does your initiative align with our focus areas and how will it change lives?",
|
287 |
+
value=st.session_state.alignment, height=100)
|
288 |
+
|
289 |
+
role_name = st.text_input("Role Name", value=st.session_state.role_name)
|
290 |
+
role_description = st.text_area("Role Description", value=st.session_state.role_description, height=100)
|
291 |
+
|
292 |
+
start_date = st.date_input("Start Date", value=st.session_state.start_date)
|
293 |
+
end_date = st.date_input("End Date", value=st.session_state.end_date)
|
294 |
+
deadline_date = st.date_input("Deadline Date", value=st.session_state.deadline_date)
|
295 |
+
|
296 |
+
beneficiary_group = st.selectbox("Beneficiary Group", options=beneficiary_groups,
|
297 |
+
index=beneficiary_groups.index(st.session_state.beneficiary_group)
|
298 |
+
if st.session_state.beneficiary_group in beneficiary_groups else 0)
|
299 |
+
|
300 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
301 |
+
|
302 |
+
# Submit button
|
303 |
+
col1, col2, col3 = st.columns([1, 2, 1])
|
304 |
+
with col2:
|
305 |
+
if st.button("Submit Initiative", key="submit_button"):
|
306 |
+
# Here you would add code to save the initiative to your database
|
307 |
+
st.success("✅ Initiative created successfully!")
|
308 |
+
# Clear form or redirect
|
309 |
+
|
310 |
+
# Add footer
|
311 |
+
st.markdown("""
|
312 |
+
---
|
313 |
+
<div style="text-align: center; color: grey; font-size: 12px;">
|
314 |
+
CSR Initiative Portal powered by Azure OpenAI - © 2025
|
315 |
+
</div>
|
316 |
+
""", unsafe_allow_html=True)
|