BasitAliii commited on
Commit
634bf96
Β·
verified Β·
1 Parent(s): 7383f67

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +179 -211
app.py CHANGED
@@ -15,7 +15,7 @@ from matching import calculate_local_matches
15
  from feedback import save_feedback
16
  from ui_styles import BASE_CSS
17
 
18
- # ---------------- Streamlit Config ----------------
19
  st.set_page_config(
20
  page_title="AI Skill Swap",
21
  page_icon="🀝",
@@ -24,26 +24,9 @@ st.set_page_config(
24
 
25
  st.markdown(f"<style>{BASE_CSS}</style>", unsafe_allow_html=True)
26
 
27
- # ---------------- Session State Defaults ----------------
28
- def init_session_state():
29
- defaults = {
30
- "username_input": "",
31
- "offers_text_input": "",
32
- "wants_text_input": "",
33
- "availability_input": "",
34
- "preferences_input": "",
35
- "feedback_input": "",
36
- }
37
- for k, v in defaults.items():
38
- if k not in st.session_state:
39
- st.session_state[k] = v
40
-
41
- init_session_state()
42
-
43
- # ---------------- Store ----------------
44
  store = ProfileStore()
45
 
46
- # ---------------- Header ----------------
47
  col_logo, col_title = st.columns([1, 6])
48
  with col_logo:
49
  st.image(
@@ -54,161 +37,176 @@ with col_title:
54
  st.title("AI Skill Swap")
55
  st.caption("Teach what you know β€’ Learn what you love β€’ Match intelligently")
56
 
57
- # ---------------- Tabs ----------------
58
- tabs = st.tabs(["Community", "AI Matchmaking", "About"])
 
59
 
60
- # ---------------- Tab 1: Community ----------------
61
- with tabs[0]:
62
- st.header("πŸ‘₯ Community Profiles")
63
 
64
- # Sidebar-like form inside tab
65
- with st.expander("πŸ“ Create / Update Profile", expanded=True):
66
- profiles = store.load_all()
67
- usernames = [p.username for p in profiles]
68
 
69
- selected_user = st.selectbox(
70
- "Select profile",
71
- ["β€” Create New β€”"] + usernames,
72
- key="selected_user"
73
- )
74
 
75
- # Input fields
76
- username = st.text_input(
77
- "Username",
78
- value=st.session_state["username_input"],
79
- key="username_input"
80
- )
81
- offers_text = st.text_area(
82
- "Skills you can teach",
83
- height=80,
84
- value=st.session_state["offers_text_input"],
85
- placeholder="Python, React, Data Science",
86
- key="offers_text_input"
87
- )
88
- wants_text = st.text_area(
89
- "Skills you want to learn",
90
- height=80,
91
- value=st.session_state["wants_text_input"],
92
- placeholder="LLMs, DevOps, UI Design",
93
- key="wants_text_input"
94
- )
95
- availability = st.text_input(
96
- "Availability",
97
- placeholder="Evenings / Weekends",
98
- value=st.session_state["availability_input"],
99
- key="availability_input"
100
- )
101
- preferences = st.text_input(
102
- "Language / Preferences",
103
- placeholder="English, Hindi, Urdu",
104
- value=st.session_state["preferences_input"],
105
- key="preferences_input"
106
- )
107
- avatar_file = st.file_uploader(
108
- "Upload Avatar (Max 800MB)",
109
- type=["png", "jpg", "jpeg"]
110
- )
111
 
112
- # Avatar preview
113
- if avatar_file:
114
- st.image(avatar_file, width=150, caption="Preview of avatar")
115
- if avatar_file.size > 800 * 1024 * 1024:
116
- st.error("File exceeds maximum size of 800MB!")
117
- avatar_file = None
118
-
119
- # Buttons
120
- col1, col2, col3 = st.columns([1, 1, 1])
121
-
122
- with col1:
123
- if st.button("βž• Create", use_container_width=True):
124
- if not username.strip():
125
- st.error("Username is required!")
126
- elif store.find_by_username(username):
127
- st.error("Profile already exists. Use Update.")
128
- else:
129
- avatar_path = None
130
- if avatar_file:
131
- safe = re.sub(r"[^A-Za-z0-9_.-]", "_", username)
132
- ext = Path(avatar_file.name).suffix
133
- avatar_path = str(
134
- AVATAR_DIR / f"{safe}_{int(time.time())}{ext}"
135
- )
136
- with open(avatar_path, "wb") as f:
137
- f.write(avatar_file.getbuffer())
138
-
139
- profile = Profile(
140
- id=str(uuid.uuid4()),
141
- username=username.strip(),
142
- offers=normalize_skill_list(offers_text),
143
- wants=normalize_skill_list(wants_text),
144
- availability=availability.strip(),
145
- preferences=preferences.strip(),
146
- avatar=avatar_path,
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  )
 
 
148
 
149
- ok, msg = store.add_or_update(profile)
150
- if ok:
151
- st.success(msg)
152
- # Clear inputs after creation
153
- st.session_state["username_input"] = ""
154
- st.session_state["offers_text_input"] = ""
155
- st.session_state["wants_text_input"] = ""
156
- st.session_state["availability_input"] = ""
157
- st.session_state["preferences_input"] = ""
158
- st.experimental_rerun()
159
- else:
160
- st.error(msg)
161
-
162
- with col2:
163
- if st.button("πŸ’Ύ Update", use_container_width=True):
164
- existing = store.find_by_username(username)
165
- if not existing:
166
- st.error("Profile does not exist. Use Create.")
 
 
167
  else:
168
- existing.offers = normalize_skill_list(offers_text)
169
- existing.wants = normalize_skill_list(wants_text)
170
- existing.availability = availability.strip()
171
- existing.preferences = preferences.strip()
172
- if avatar_file:
173
- safe = re.sub(r"[^A-Za-z0-9_.-]", "_", username)
174
- ext = Path(avatar_file.name).suffix
175
- avatar_path = str(
176
- AVATAR_DIR / f"{safe}_{int(time.time())}{ext}"
177
- )
178
- with open(avatar_path, "wb") as f:
179
- f.write(avatar_file.getbuffer())
180
- existing.avatar = avatar_path
181
-
182
- store.add_or_update(existing)
183
- st.success("Profile updated")
184
- st.experimental_rerun()
185
-
186
- with col3:
187
- if selected_user != "β€” Create New β€”":
188
- if st.button("πŸ—‘οΈ Delete Profile", use_container_width=True):
189
- store.delete(selected_user)
190
- st.warning("Profile deleted")
191
- st.session_state["username_input"] = ""
192
- st.session_state["offers_text_input"] = ""
193
- st.session_state["wants_text_input"] = ""
194
- st.session_state["availability_input"] = ""
195
- st.session_state["preferences_input"] = ""
196
- st.experimental_rerun()
197
-
198
- # Display community profiles
199
- st.subheader("🌍 All Profiles")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  profiles = store.load_all()
 
201
  if not profiles:
202
- st.info("No profiles yet. Create one from the form above.")
203
  else:
204
  for p in profiles:
205
  st.markdown("<div class='card'>", unsafe_allow_html=True)
206
  cols = st.columns([1, 4])
 
207
  with cols[0]:
208
  if p.avatar and Path(p.avatar).exists():
209
  st.image(p.avatar, width=120)
210
  else:
211
- st.image("https://via.placeholder.com/120", width=120)
 
 
 
 
212
  with cols[1]:
213
  st.markdown(f"**{p.username}**")
214
  st.caption(f"{p.availability} β€’ {p.preferences}")
@@ -216,26 +214,32 @@ with tabs[0]:
216
  st.markdown(f"🎯 **Wants:** {', '.join(p.wants) or 'β€”'}")
217
  st.markdown("</div>", unsafe_allow_html=True)
218
 
219
- # ---------------- Tab 2: AI Matchmaking ----------------
220
- with tabs[1]:
221
- st.header("πŸ€– AI Matchmaking")
222
- profiles = store.load_all()
223
  if not profiles:
224
  st.info("Add profiles to enable matchmaking.")
225
  else:
226
- pick = st.selectbox("Choose your profile", [p.username for p in profiles])
 
 
 
 
227
  if st.button("✨ Find Best Matches"):
228
  with st.spinner("Analyzing profiles..."):
229
  current = store.find_by_username(pick)
 
230
  try:
231
  sys_msg, user_msg = make_prompt_for_matching(current, profiles, 3)
232
  matches = ask_groq_for_matches(sys_msg, user_msg)
233
  except Exception:
234
  matches = calculate_local_matches(
235
- current, [p for p in profiles if p.id != current.id], 3
 
 
236
  )
237
 
238
- st.subheader("Top Matches")
239
  for m in matches:
240
  st.markdown("<div class='card'>", unsafe_allow_html=True)
241
  st.markdown(f"### {m.get('username')}")
@@ -245,57 +249,21 @@ with tabs[1]:
245
  st.caption(m.get("reason", "AI Match"))
246
  st.markdown("</div>", unsafe_allow_html=True)
247
 
248
- # ---------------- Tab 3: About ----------------
249
- with tabs[2]:
250
- st.header("ℹ️ About AI Skill Swap")
251
- st.markdown("""
252
- **AI Skill Swap** is a platform designed to help people **teach and learn skills** from each other.
253
-
254
- **Purpose:**
255
- - Connect learners with mentors in your community.
256
- - Discover new skills efficiently using AI matchmaking.
257
- - Encourage skill exchange without formal courses.
258
-
259
- **Features:**
260
- - Create, Update, Delete personal profiles with skills, availability, and preferences.
261
- - AI-based matchmaking to find top compatible skill partners.
262
- - Community tab to see all profiles.
263
- - Feedback submission for continuous improvement.
264
-
265
- **Technologies Used:**
266
- - Python, Streamlit (Frontend & Backend)
267
- - Groq API / Local AI Matching
268
- - File handling for avatar uploads
269
- - Persistent storage (ProfileStore)
270
-
271
- **Why it's helpful:**
272
- - Makes skill exchange easy and organized.
273
- - Learners and teachers connect automatically with AI suggestions.
274
- - Saves time compared to manually searching for mentors or learners.
275
-
276
- **How to Use:**
277
- 1. Create a profile in the Community tab.
278
- 2. View other community profiles.
279
- 3. Go to AI Matchmaking tab to find best skill partners.
280
- 4. Submit feedback via sidebar form.
281
-
282
- *Enjoy teaching, learning, and growing together!*
283
- """)
284
-
285
- # ---------------- Feedback Sidebar ----------------
286
  with st.sidebar:
287
  st.markdown("---")
288
  with st.expander("πŸ’¬ Feedback"):
289
- fb = st.text_area("Your feedback", value=st.session_state["feedback_input"], key="feedback_input")
290
- if st.button("Submit Feedback", key="submit_feedback"):
291
  if fb.strip():
292
- save_feedback({
293
- "timestamp": time.time(),
294
- "feedback": fb.strip(),
295
- "username": st.session_state.get("username_input", "anonymous")
296
- })
297
- st.success("Thank you for your feedback!")
298
- st.session_state["feedback_input"] = ""
299
- st.experimental_rerun()
 
300
  else:
301
  st.warning("Please write feedback.")
 
15
  from feedback import save_feedback
16
  from ui_styles import BASE_CSS
17
 
18
+ # ---------- Streamlit Config ----------
19
  st.set_page_config(
20
  page_title="AI Skill Swap",
21
  page_icon="🀝",
 
24
 
25
  st.markdown(f"<style>{BASE_CSS}</style>", unsafe_allow_html=True)
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  store = ProfileStore()
28
 
29
+ # ---------- Header ----------
30
  col_logo, col_title = st.columns([1, 6])
31
  with col_logo:
32
  st.image(
 
37
  st.title("AI Skill Swap")
38
  st.caption("Teach what you know β€’ Learn what you love β€’ Match intelligently")
39
 
40
+ # ---------- Sidebar: Profile Management ----------
41
+ with st.sidebar:
42
+ st.header("πŸ‘€ Profile Manager")
43
 
44
+ profiles = store.load_all()
45
+ usernames = [p.username for p in profiles]
 
46
 
47
+ selected_user = st.selectbox(
48
+ "Select profile",
49
+ ["β€” Create New β€”"] + usernames,
50
+ )
51
 
52
+ st.markdown("### πŸ“ Profile Details")
53
+ username = st.text_input(
54
+ "Username",
55
+ value="" if selected_user == "β€” Create New β€”" else selected_user,
56
+ )
57
 
58
+ offers_text = st.text_area(
59
+ "Skills you can teach",
60
+ height=90,
61
+ value=st.session_state.get("offers_text", ""),
62
+ placeholder="Python, React, Data Science",
63
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ wants_text = st.text_area(
66
+ "Skills you want to learn",
67
+ height=90,
68
+ value=st.session_state.get("wants_text", ""),
69
+ placeholder="LLMs, DevOps, UI Design",
70
+ )
71
+
72
+ availability = st.text_input(
73
+ "Availability",
74
+ placeholder="Evenings / Weekends",
75
+ value=st.session_state.get("availability", ""),
76
+ )
77
+
78
+ preferences = st.text_input(
79
+ "Language / Preferences",
80
+ placeholder="English, Hindi, Urdu",
81
+ value=st.session_state.get("preferences", ""),
82
+ )
83
+
84
+ avatar_file = st.file_uploader(
85
+ "Upload Avatar (Max 800MB)",
86
+ type=["png", "jpg", "jpeg"],
87
+ )
88
+
89
+ # ---------- Avatar Preview ----------
90
+ if avatar_file:
91
+ st.image(avatar_file, width=150, caption="Preview of avatar")
92
+ if avatar_file.size > 800 * 1024 * 1024:
93
+ st.error("File exceeds maximum size of 800MB!")
94
+ avatar_file = None
95
+
96
+ st.markdown("---")
97
+ col_create, col_update = st.columns(2)
98
+
99
+ # ---------- CREATE PROFILE ----------
100
+ with col_create:
101
+ if st.button("βž• Create", use_container_width=True):
102
+ if not username.strip():
103
+ st.error("Username is required!")
104
+ elif store.find_by_username(username):
105
+ st.error("Profile already exists. Use Update.")
106
+ else:
107
+ avatar_path = None
108
+ if avatar_file:
109
+ safe = re.sub(r"[^A-Za-z0-9_.-]", "_", username)
110
+ ext = Path(avatar_file.name).suffix
111
+ avatar_path = str(
112
+ AVATAR_DIR / f"{safe}_{int(time.time())}{ext}"
113
  )
114
+ with open(avatar_path, "wb") as f:
115
+ f.write(avatar_file.getbuffer())
116
 
117
+ profile = Profile(
118
+ id=str(uuid.uuid4()),
119
+ username=username.strip(),
120
+ offers=normalize_skill_list(offers_text),
121
+ wants=normalize_skill_list(wants_text),
122
+ availability=availability.strip(),
123
+ preferences=preferences.strip(),
124
+ avatar=avatar_path,
125
+ )
126
+
127
+ ok, msg = store.add_or_update(profile)
128
+ if ok:
129
+ st.success(msg)
130
+ # Clear all fields after creation
131
+ st.session_state["username"] = ""
132
+ st.session_state["offers_text"] = ""
133
+ st.session_state["wants_text"] = ""
134
+ st.session_state["availability"] = ""
135
+ st.session_state["preferences"] = ""
136
+ st.rerun()
137
  else:
138
+ st.error(msg)
139
+
140
+ # ---------- UPDATE PROFILE ----------
141
+ with col_update:
142
+ if st.button("πŸ’Ύ Update", use_container_width=True):
143
+ existing = store.find_by_username(username)
144
+ if not existing:
145
+ st.error("Profile does not exist. Use Create.")
146
+ else:
147
+ existing.offers = normalize_skill_list(offers_text)
148
+ existing.wants = normalize_skill_list(wants_text)
149
+ existing.availability = availability.strip()
150
+ existing.preferences = preferences.strip()
151
+
152
+ if avatar_file:
153
+ safe = re.sub(r"[^A-Za-z0-9_.-]", "_", username)
154
+ ext = Path(avatar_file.name).suffix
155
+ avatar_path = str(
156
+ AVATAR_DIR / f"{safe}_{int(time.time())}{ext}"
157
+ )
158
+ with open(avatar_path, "wb") as f:
159
+ f.write(avatar_file.getbuffer())
160
+ existing.avatar = avatar_path
161
+
162
+ store.add_or_update(existing)
163
+ st.success("Profile updated")
164
+ # Keep fields after update
165
+ st.session_state["username"] = username
166
+ st.session_state["offers_text"] = offers_text
167
+ st.session_state["wants_text"] = wants_text
168
+ st.session_state["availability"] = availability
169
+ st.session_state["preferences"] = preferences
170
+ st.rerun()
171
+
172
+ # ---------- DELETE PROFILE ----------
173
+ if selected_user != "β€” Create New β€”":
174
+ if st.button("πŸ—‘οΈ Delete Profile", type="secondary"):
175
+ store.delete(selected_user)
176
+ st.warning("Profile deleted")
177
+ # Clear fields after deletion
178
+ st.session_state["username"] = ""
179
+ st.session_state["offers_text"] = ""
180
+ st.session_state["wants_text"] = ""
181
+ st.session_state["availability"] = ""
182
+ st.session_state["preferences"] = ""
183
+ time.sleep(0.3)
184
+ st.rerun()
185
+
186
+ # ---------- MAIN CONTENT ----------
187
+ left, right = st.columns([2, 3])
188
+
189
+ # ---------- COMMUNITY PROFILES ----------
190
+ with left:
191
+ st.subheader("🌍 Community Profiles")
192
  profiles = store.load_all()
193
+
194
  if not profiles:
195
+ st.info("No profiles yet. Create one from the sidebar.")
196
  else:
197
  for p in profiles:
198
  st.markdown("<div class='card'>", unsafe_allow_html=True)
199
  cols = st.columns([1, 4])
200
+
201
  with cols[0]:
202
  if p.avatar and Path(p.avatar).exists():
203
  st.image(p.avatar, width=120)
204
  else:
205
+ st.image(
206
+ "https://via.placeholder.com/120",
207
+ width=120,
208
+ )
209
+
210
  with cols[1]:
211
  st.markdown(f"**{p.username}**")
212
  st.caption(f"{p.availability} β€’ {p.preferences}")
 
214
  st.markdown(f"🎯 **Wants:** {', '.join(p.wants) or 'β€”'}")
215
  st.markdown("</div>", unsafe_allow_html=True)
216
 
217
+ # ---------- AI MATCHMAKING ----------
218
+ with right:
219
+ st.subheader("πŸ€– AI Matchmaking")
220
+
221
  if not profiles:
222
  st.info("Add profiles to enable matchmaking.")
223
  else:
224
+ pick = st.selectbox(
225
+ "Choose your profile",
226
+ [p.username for p in profiles],
227
+ )
228
+
229
  if st.button("✨ Find Best Matches"):
230
  with st.spinner("Analyzing profiles..."):
231
  current = store.find_by_username(pick)
232
+
233
  try:
234
  sys_msg, user_msg = make_prompt_for_matching(current, profiles, 3)
235
  matches = ask_groq_for_matches(sys_msg, user_msg)
236
  except Exception:
237
  matches = calculate_local_matches(
238
+ current,
239
+ [p for p in profiles if p.id != current.id],
240
+ 3,
241
  )
242
 
 
243
  for m in matches:
244
  st.markdown("<div class='card'>", unsafe_allow_html=True)
245
  st.markdown(f"### {m.get('username')}")
 
249
  st.caption(m.get("reason", "AI Match"))
250
  st.markdown("</div>", unsafe_allow_html=True)
251
 
252
+ # ---------- FEEDBACK ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  with st.sidebar:
254
  st.markdown("---")
255
  with st.expander("πŸ’¬ Feedback"):
256
+ fb = st.text_area("Your feedback")
257
+ if st.button("Submit Feedback"):
258
  if fb.strip():
259
+ save_feedback(
260
+ {
261
+ "timestamp": time.time(),
262
+ "feedback": fb,
263
+ "username": st.session_state.get("username", "anonymous"),
264
+ }
265
+ )
266
+ st.success("Thank you!")
267
+ st.rerun()
268
  else:
269
  st.warning("Please write feedback.")