BasitAliii's picture
Update app.py
09b864a verified
raw
history blame
9.26 kB
from __future__ import annotations
import time
import uuid
import re
from pathlib import Path
import streamlit as st
from config import AVATAR_DIR
from models import Profile
from storage import 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 ----------
col_logo, col_title = st.columns([1, 6])
with col_logo:
try:
st.image("logo.jpg", width=90) # use uploaded logo
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,
)
st.markdown("### πŸ“ Profile Details")
username = st.text_input(
"Username",
value="" if selected_user == "β€” Create New β€”" else selected_user,
)
offers_text = st.text_area(
"Skills you can teach",
height=90,
value=st.session_state.get("offers_text", ""),
placeholder="Python, React, Data Science",
)
wants_text = st.text_area(
"Skills you want to learn",
height=90,
value=st.session_state.get("wants_text", ""),
placeholder="LLMs, DevOps, UI Design",
)
availability = st.text_input(
"Availability",
placeholder="Evenings / Weekends",
value=st.session_state.get("availability", ""),
)
preferences = st.text_input(
"Language / Preferences",
placeholder="English, Hindi, Urdu",
value=st.session_state.get("preferences", ""),
)
avatar_file = st.file_uploader(
"Upload Avatar (Max 800MB)",
type=["png", "jpg", "jpeg"],
)
# ---------- Avatar Preview ----------
if avatar_file:
st.image(avatar_file, width=150, caption="Preview of avatar")
if avatar_file.size > 800 * 1024 * 1024:
st.error("File exceeds maximum size of 800MB!")
avatar_file = None
st.markdown("---")
col_create, col_update = st.columns(2)
# ---------- CREATE PROFILE ----------
with col_create:
if st.button("βž• Create", use_container_width=True):
if not username.strip():
st.error("Username is required!")
elif store.find_by_username(username):
st.error("Profile 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 all fields after create
for key in ["username", "offers_text", "wants_text", "availability", "preferences"]:
st.session_state[key] = ""
# Refresh page
st.experimental_set_query_params(refresh=str(uuid.uuid4()))
else:
st.error(msg)
# ---------- UPDATE PROFILE ----------
with col_update:
if st.button("πŸ’Ύ Update", use_container_width=True):
existing = store.find_by_username(username)
if not existing:
st.error("Profile does not exist. Use Create.")
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")
# Keep fields intact, just refresh profile list
st.experimental_set_query_params(refresh=str(uuid.uuid4()))
# ---------- DELETE PROFILE ----------
if selected_user != "β€” Create New β€”":
if st.button("πŸ—‘οΈ Delete Profile", type="secondary"):
store.delete(selected_user)
st.warning("Profile deleted")
# Clear session state
for key in ["username", "offers_text", "wants_text", "availability", "preferences"]:
st.session_state[key] = ""
st.experimental_set_query_params(refresh=str(uuid.uuid4()))
# ---------- MAIN CONTENT ----------
left, right = st.columns([2, 3])
# ---------- COMMUNITY PROFILES ----------
with left:
st.subheader("🌍 Community Profiles")
profiles = store.load_all()
if not profiles:
st.info("No profiles yet. Create one from the sidebar.")
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(
"Choose your profile",
[p.username for p in profiles],
)
if st.button("✨ Find Best Matches"):
with st.spinner("Analyzing profiles..."):
current = store.find_by_username(pick)
try:
sys_msg, user_msg = make_prompt_for_matching(current, profiles, 3)
matches = ask_groq_for_matches(sys_msg, user_msg)
except Exception:
matches = calculate_local_matches(
current,
[p for p in profiles if p.id != current.id],
3,
)
for m in matches:
st.markdown("<div class='card'>", unsafe_allow_html=True)
st.markdown(f"### {m.get('username')}")
score = float(m.get("score", 0))
st.progress(score / 100)
st.caption(f"Match Score: {score}%")
st.caption(m.get("reason", "AI Match"))
st.markdown("</div>", unsafe_allow_html=True)
# ---------- FEEDBACK ----------
with st.sidebar:
st.markdown("---")
with st.expander("πŸ’¬ Feedback"):
fb = st.text_area("Your feedback")
if st.button("Submit Feedback"):
if fb.strip():
save_feedback(
{
"timestamp": time.time(),
"feedback": fb,
"username": st.session_state.get("username", "anonymous"),
}
)
st.success("Thank you!")
st.experimental_set_query_params(refresh=str(uuid.uuid4()))
else:
st.warning("Please write feedback.")