BasitAliii's picture
Update app.py
d675e08 verified
from __future__ import annotations
import time
import uuid
import re
from pathlib import Path
import streamlit as st
from config import AVATAR_DIR
from storage import Profile, ProfileStore
from utils import normalize_skill_list, make_prompt_for_matching
from groq_client import ask_groq_for_matches
from matching import calculate_local_matches
from feedback import save_feedback
from ui_styles import BASE_CSS
# ----------------------------------------------
# Streamlit Config
# ----------------------------------------------
st.set_page_config(
page_title="AI Skill Swap",
page_icon="🀝",
layout="wide",
)
st.markdown(f"<style>{BASE_CSS}</style>", unsafe_allow_html=True)
store = ProfileStore()
# ----------------------------------------------
# Header Section
# ----------------------------------------------
col_logo, col_title = st.columns([1, 6])
with col_logo:
try:
st.image("logo.jpg", width=90)
except:
st.image("https://via.placeholder.com/100x100/4F46E5/FFFFFF?text=🀝", width=90)
with col_title:
st.title("AI Skill Swap")
st.caption("Teach what you know β€’ Learn what you love β€’ Match intelligently")
# ----------------------------------------------
# Sidebar: Profile Management
# ----------------------------------------------
with st.sidebar:
st.header("πŸ‘€ Profile Manager")
profiles = store.load_all()
usernames = [p.username for p in profiles]
selected_user = st.selectbox(
"Select profile",
["β€” Create New β€”"] + usernames,
key="selected_user"
)
st.markdown("### πŸ“ Profile Details")
username = st.text_input(
"Username",
value="" if selected_user == "β€” Create New β€”" else selected_user,
key="username_input"
)
offers_text = st.text_area(
"Skills you can teach",
height=90,
value=st.session_state.get("offers_text", ""),
placeholder="Python, React, Data Science",
key="offers_input"
)
wants_text = st.text_area(
"Skills you want to learn",
height=90,
value=st.session_state.get("wants_text", ""),
placeholder="LLMs, DevOps, UI Design",
key="wants_input"
)
availability = st.text_input(
"Availability",
placeholder="Evenings / Weekends",
value=st.session_state.get("availability", ""),
key="availability_input"
)
preferences = st.text_input(
"Language / Preferences",
placeholder="English, Hindi, Urdu",
value=st.session_state.get("preferences", ""),
key="preferences_input"
)
avatar_file = st.file_uploader(
"Upload Avatar (Max 800KB)",
type=["png", "jpg", "jpeg"],
key="avatar_uploader"
)
# Avatar Preview & Size Check
if avatar_file:
st.image(avatar_file, width=150, caption="Preview Avatar")
if avatar_file.size > 800 * 1024:
st.error("File exceeds 800KB!")
avatar_file = None
st.markdown("---")
col_create, col_update = st.columns(2)
# Create Button
with col_create:
if st.button("βž• Create", key="create_button", use_container_width=True):
if not username.strip():
st.error("Username is required!")
elif store.find_by_username(username):
st.error("Already exists β€” use Update!")
else:
avatar_path = None
if avatar_file:
safe = re.sub(r"[^A-Za-z0-9_.-]", "_", username)
ext = Path(avatar_file.name).suffix
avatar_path = str(
AVATAR_DIR / f"{safe}_{int(time.time())}{ext}"
)
with open(avatar_path, "wb") as f:
f.write(avatar_file.getbuffer())
profile = Profile(
id=str(uuid.uuid4()),
username=username.strip(),
offers=normalize_skill_list(offers_text),
wants=normalize_skill_list(wants_text),
availability=availability.strip(),
preferences=preferences.strip(),
avatar=avatar_path,
)
ok, msg = store.add_or_update(profile)
if ok:
st.success(msg)
# Clear fields on create
for key in ["offers_text", "wants_text", "availability", "preferences"]:
if key in st.session_state:
del st.session_state[key]
if "avatar_uploader" in st.session_state:
del st.session_state["avatar_uploader"]
# Clear previous matches
st.session_state["matches"] = []
st.rerun()
else:
st.error(msg)
# Update Button
with col_update:
if st.button("πŸ’Ύ Update", key="update_button", use_container_width=True):
existing = store.find_by_username(username)
if not existing:
st.error("Profile does not exist.")
else:
existing.offers = normalize_skill_list(offers_text)
existing.wants = normalize_skill_list(wants_text)
existing.availability = availability.strip()
existing.preferences = preferences.strip()
if avatar_file:
safe = re.sub(r"[^A-Za-z0-9_.-]", "_", username)
ext = Path(avatar_file.name).suffix
avatar_path = str(
AVATAR_DIR / f"{safe}_{int(time.time())}{ext}"
)
with open(avatar_path, "wb") as f:
f.write(avatar_file.getbuffer())
existing.avatar = avatar_path
store.add_or_update(existing)
st.success("Profile updated")
if "avatar_uploader" in st.session_state:
del st.session_state["avatar_uploader"]
st.rerun()
# Delete Button
if selected_user != "β€” Create New β€”":
if st.button("πŸ—‘οΈ Delete Profile", key="delete_button", type="secondary"):
ok, msg = store.delete(selected_user)
if ok:
st.warning(msg)
# Clear sidebar fields
for key in ["username_input", "offers_input", "wants_input", "availability_input", "preferences_input"]:
if key in st.session_state:
del st.session_state[key]
if "avatar_uploader" in st.session_state:
del st.session_state["avatar_uploader"]
st.rerun()
else:
st.error(msg)
# ----------------------------------------------
# Main Content - Community Profiles
# ----------------------------------------------
left, right = st.columns([2, 3])
with left:
st.subheader("🌍 Community Profiles")
profiles = store.load_all()
if not profiles:
st.info("No profiles yet. Create one above.")
else:
for p in profiles:
st.markdown("<div class='card'>", unsafe_allow_html=True)
cols = st.columns([1, 4])
with cols[0]:
if p.avatar and Path(p.avatar).exists():
st.image(p.avatar, width=120)
else:
st.image("https://via.placeholder.com/120", width=120)
with cols[1]:
st.markdown(f"**{p.username}**")
st.caption(f"{p.availability} β€’ {p.preferences}")
st.markdown(f"🧠 **Offers:** {', '.join(p.offers) or 'β€”'}")
st.markdown(f"🎯 **Wants:** {', '.join(p.wants) or 'β€”'}")
st.markdown("</div>", unsafe_allow_html=True)
# ----------------------------------------------
# AI Matchmaking
# ----------------------------------------------
with right:
st.subheader("πŸ€– AI Matchmaking")
if not profiles:
st.info("Add profiles to enable matchmaking.")
else:
pick = st.selectbox(
"Match for profile",
[p.username for p in profiles],
key="matchmaking_select"
)
if st.button("✨ Find Best Matches", key="find_matches_button"):
with st.spinner("Finding best matches..."):
current = store.find_by_username(pick)
try:
sys_msg, user_msg = make_prompt_for_matching(
current, profiles, 3
)
raw_matches = ask_groq_for_matches(sys_msg, user_msg)
except Exception as e:
st.info(
f"Using local matching due to: {str(e)[:100]}..."
)
raw_matches = calculate_local_matches(
current,
[p for p in profiles if p.id != current.id],
3,
)
enriched = []
for rm in raw_matches:
prof = store.find_by_username(rm.get("username", ""))
if not prof:
continue
# πŸ”’ SAFE SCORE HANDLING
try:
raw_score = float(rm.get("score", 0))
except (ValueError, TypeError):
raw_score = 0.0
raw_score = max(0.0, min(raw_score, 1.0))
enriched.append({
"username": prof.username,
"offers": prof.offers,
"wants": prof.wants,
"avatar": prof.avatar,
"score": raw_score, # ALWAYS 0–1
"reason": rm.get("reason", "AI Match")
})
st.session_state["matches"] = enriched
matches_list = st.session_state.get("matches", [])
if matches_list:
st.markdown(
f"### 🎯 Top {len(matches_list)} Matches for {pick}"
)
for m in matches_list:
st.markdown("<div class='card'>", unsafe_allow_html=True)
cols = st.columns([1, 4])
with cols[0]:
if m.get("avatar") and Path(m["avatar"]).exists():
st.image(m["avatar"], width=120)
else:
st.image(
"https://via.placeholder.com/120",
width=120
)
with cols[1]:
st.markdown(f"### {m['username']}")
score_float = m["score"] # 0–1
score_percent = int(score_float * 100)
st.progress(score_float)
st.caption(f"Match Score: {score_percent}%")
st.markdown(
f"🧠 **Offers:** "
f"{', '.join(m['offers']) or 'β€”'}"
)
st.markdown(
f"🎯 **Wants:** "
f"{', '.join(m['wants']) or 'β€”'}"
)
st.markdown(f"*{m['reason']}*")
st.markdown("</div>", unsafe_allow_html=True)
# ----------------------------------------------
# Feedback
# ----------------------------------------------
with st.sidebar:
st.markdown("---")
with st.expander("πŸ’¬ Feedback"):
fb = st.text_area("Your feedback", key="feedback_text")
if st.button("Submit Feedback", key="submit_feedback_button"):
if fb.strip():
save_feedback({
"timestamp": time.time(),
"feedback": fb,
"username": username if username.strip() else "anonymous",
})
st.success("Thank you!")
if "feedback_text" in st.session_state:
del st.session_state["feedback_text"]
st.rerun()
else:
st.warning("Please write feedback.")