Spaces:
Runtime error
Runtime error
| """Practice Fields Hub β Curated Therapeutic Practice Spaces""" | |
| import streamlit as st | |
| import base64 | |
| import os | |
| st.set_page_config(page_title="Practice Fields", page_icon="π±", layout="wide") | |
| def get_image_base64(image_path): | |
| """Convert image to base64 for embedding in HTML.""" | |
| try: | |
| with open(image_path, "rb") as f: | |
| return base64.b64encode(f.read()).decode() | |
| except: | |
| return None | |
| # Get base path for images | |
| BASE_PATH = os.path.dirname(os.path.abspath(__file__)) | |
| IMG_PATH = os.path.join(BASE_PATH, "images") | |
| st.markdown(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap'); | |
| .stApp { | |
| background: linear-gradient(160deg, #e8f4f8 0%, #f0faf0 30%, #fdfbf4 60%, #f8f4ff 100%); | |
| font-family: 'Inter', sans-serif; | |
| } | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| .hub-header { | |
| text-align: center; | |
| padding: 3rem 2rem 2rem 2rem; | |
| margin-bottom: 1rem; | |
| } | |
| .hub-logo { | |
| font-size: 4rem; | |
| margin-bottom: 1rem; | |
| filter: drop-shadow(0 4px 8px rgba(100, 160, 140, 0.2)); | |
| } | |
| .hub-title { | |
| color: #3a5050; | |
| font-size: 2.8rem; | |
| font-weight: 300; | |
| letter-spacing: -0.02em; | |
| margin-bottom: 0.75rem; | |
| } | |
| .hub-tagline { | |
| color: #6a8080; | |
| font-size: 1.15rem; | |
| font-weight: 300; | |
| font-style: italic; | |
| } | |
| .clinician-badge { | |
| background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(240,250,245,0.9) 100%); | |
| border-radius: 20px; | |
| padding: 1.25rem 2rem; | |
| text-align: center; | |
| margin: 1.5rem auto 2rem auto; | |
| max-width: 450px; | |
| border: 1px solid rgba(160, 200, 180, 0.3); | |
| box-shadow: 0 4px 20px rgba(100, 160, 140, 0.1); | |
| } | |
| .clinician-name { | |
| color: #3a5050; | |
| font-size: 1.2rem; | |
| font-weight: 500; | |
| margin-bottom: 0.25rem; | |
| } | |
| .clinician-orientation { | |
| color: #7a9a9a; | |
| font-size: 0.95rem; | |
| font-weight: 300; | |
| } | |
| .field-card { | |
| background: linear-gradient(145deg, rgba(255,255,255,0.95) 0%, rgba(252,255,253,0.95) 100%); | |
| border-radius: 20px; | |
| padding: 1.5rem; | |
| margin: 0.6rem 0; | |
| border: 1px solid rgba(180, 210, 195, 0.25); | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| text-decoration: none; | |
| display: block; | |
| color: inherit; | |
| box-shadow: 0 2px 12px rgba(100, 140, 130, 0.06); | |
| } | |
| .field-card:hover { | |
| transform: translateY(-4px); | |
| box-shadow: 0 12px 32px rgba(100, 160, 140, 0.15); | |
| border-color: rgba(140, 190, 170, 0.4); | |
| } | |
| .field-icon { | |
| width: 80px; | |
| height: 80px; | |
| object-fit: contain; | |
| margin-bottom: 0.75rem; | |
| border-radius: 12px; | |
| } | |
| .field-title { | |
| color: #3a5555; | |
| font-size: 1.2rem; | |
| font-weight: 500; | |
| margin-bottom: 0.4rem; | |
| } | |
| .field-desc { | |
| color: #6a8585; | |
| font-size: 0.9rem; | |
| line-height: 1.5; | |
| font-weight: 300; | |
| } | |
| .section-header { | |
| color: #5a7a7a; | |
| font-size: 0.85rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.15em; | |
| font-weight: 500; | |
| margin: 2.5rem 0 1.5rem 0; | |
| padding-bottom: 0.75rem; | |
| border-bottom: 2px solid rgba(160, 200, 180, 0.2); | |
| } | |
| .footer-container { | |
| text-align: center; | |
| padding: 3rem 2rem 2rem 2rem; | |
| margin-top: 3rem; | |
| background: linear-gradient(180deg, transparent 0%, rgba(230, 245, 240, 0.3) 100%); | |
| border-top: 1px solid rgba(180, 210, 195, 0.2); | |
| } | |
| .footer-main { | |
| color: #6a8a8a; | |
| font-size: 0.95rem; | |
| font-weight: 300; | |
| margin-bottom: 1rem; | |
| line-height: 1.6; | |
| } | |
| .crisis-resources { | |
| display: inline-flex; | |
| gap: 1.5rem; | |
| background: rgba(255,255,255,0.6); | |
| padding: 0.75rem 1.5rem; | |
| border-radius: 30px; | |
| } | |
| .crisis-item { | |
| color: #5a7a7a; | |
| font-size: 0.9rem; | |
| font-weight: 500; | |
| } | |
| .welcome-text { | |
| color: #4a6a6a; | |
| font-size: 1.1rem; | |
| font-weight: 300; | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| line-height: 1.6; | |
| } | |
| .stTextInput > div > div > input { | |
| background-color: rgba(255,255,255,0.9) !important; | |
| border: 1px solid rgba(180, 210, 195, 0.4) !important; | |
| border-radius: 12px !important; | |
| padding: 0.75rem 1rem !important; | |
| } | |
| .stTextArea > div > div > textarea { | |
| background-color: rgba(255,255,255,0.9) !important; | |
| border: 1px solid rgba(180, 210, 195, 0.4) !important; | |
| border-radius: 12px !important; | |
| } | |
| .stButton > button { | |
| background: linear-gradient(135deg, #7eb8a8 0%, #6aaa98 100%) !important; | |
| border: none !important; | |
| border-radius: 12px !important; | |
| color: white !important; | |
| font-weight: 500 !important; | |
| padding: 0.75rem 2rem !important; | |
| box-shadow: 0 4px 12px rgba(100, 160, 140, 0.25) !important; | |
| } | |
| .stButton > button:hover { | |
| background: linear-gradient(135deg, #6aaa98 0%, #5a9a88 100%) !important; | |
| transform: translateY(-2px) !important; | |
| } | |
| .divider { | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, rgba(160, 200, 180, 0.3), transparent); | |
| margin: 2rem 0; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| if "clinician_name" not in st.session_state: | |
| st.session_state.clinician_name = "" | |
| if "clinician_orientation" not in st.session_state: | |
| st.session_state.clinician_orientation = "" | |
| if "onboarded" not in st.session_state: | |
| st.session_state.onboarded = False | |
| # Load images | |
| IMAGES = { | |
| "shadowbox": get_image_base64(os.path.join(IMG_PATH, "shadowbox.webp")), | |
| "tendsend": get_image_base64(os.path.join(IMG_PATH, "tendsend.webp")), | |
| "buildabot": get_image_base64(os.path.join(IMG_PATH, "buildabot.webp")), | |
| "diagnosis": get_image_base64(os.path.join(IMG_PATH, "diagnosis.webp")), | |
| "gspt": get_image_base64(os.path.join(IMG_PATH, "gspt.webp")), | |
| "learnnvc": get_image_base64(os.path.join(IMG_PATH, "learnnvc.webp")), | |
| "difficultconversations": get_image_base64(os.path.join(IMG_PATH, "difficultconversations.webp")), | |
| } | |
| def img_tag(key): | |
| if IMAGES.get(key): | |
| return f'<img src="data:image/webp;base64,{IMAGES[key]}" class="field-icon" />' | |
| return "" | |
| SPACES = [ | |
| {"key": "shadowbox", "title": "ShadowBox Library", "desc": "A resonant library for hard thoughts. Psychoeducation without synthetic intimacy.", "url": "https://huggingface.co/spaces/jostlebot/shadowbox.library"}, | |
| {"key": "tendsend", "title": "Tend & Send", "desc": "Craft messages with care. Practice tending to yourself before sending.", "url": "https://huggingface.co/spaces/jostlebot/TendSend.PrototypeBot"}, | |
| {"key": "buildabot", "title": "Build a Bot", "desc": "AI literacy β understand how LLMs work, spot synthetic intimacy.", "url": "https://huggingface.co/spaces/jostlebot/BuildABot.2"}, | |
| {"key": "diagnosis", "title": "Diagnosis Explorer", "desc": "Understand diagnostic categories with nuance and care.", "url": "https://huggingface.co/spaces/jostlebot/DiagnosisExplorer"}, | |
| {"key": "gspt", "title": "GSPT", "desc": "Generating Safer Passages of Text. Warm, boundaried reflections.", "url": "https://huggingface.co/spaces/jostlebot/GSPT"}, | |
| {"key": "learnnvc", "title": "Learn NVC", "desc": "Practice Nonviolent Communication. Transform judgments into needs.", "url": "https://huggingface.co/spaces/jostlebot/LearnNVC"}, | |
| {"key": "difficultconversations", "title": "Difficult Conversations", "desc": "Rehearse hard conversations before having them for real.", "url": "https://huggingface.co/spaces/jostlebot/PracticeDifficultConversations"}, | |
| ] | |
| if not st.session_state.onboarded: | |
| st.markdown(""" | |
| <div class="hub-header"> | |
| <div class="hub-logo">π±</div> | |
| <div class="hub-title">Practice Fields</div> | |
| <div class="hub-tagline">Example Prototype</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div style="max-width: 700px; margin: 0 auto 2rem auto; text-align: center; padding: 0 1rem;"> | |
| <p style="color: #4a6a6a; font-size: 1.1rem; line-height: 1.7; font-weight: 300;"> | |
| This is a <strong>prototype demonstration</strong> of what becomes possible when mental health professionals | |
| bring clinical insight, experience, and wisdom to the design of AI-assisted tools. | |
| </p> | |
| <p style="color: #6a8a8a; font-size: 1rem; line-height: 1.7; font-weight: 300; margin-top: 1rem;"> | |
| Each space represents <strong>bounded, trauma-informed innovation</strong> β | |
| using LLMs as a relational medium with extraordinary strategic care. | |
| Not replacing therapy. Not performing synthetic intimacy. | |
| Building bridges back to human connection. | |
| </p> | |
| <p style="color: #7a9a9a; font-size: 0.95rem; line-height: 1.6; font-weight: 300; margin-top: 1.5rem; font-style: italic;"> | |
| Created by a licensed clinician exploring what ethical, clinically-grounded AI practice spaces can look like. | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown('<div class="divider"></div>', unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div class="welcome-text"> | |
| Enter as Clinician<br> | |
| <span style="font-size: 0.95rem; color: #7a9a9a;">Personalize this hub to explore the prototype spaces</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| col1, col2, col3 = st.columns([1, 2, 1]) | |
| with col2: | |
| with st.form("onboarding"): | |
| name = st.text_input("Your Name & Credentials", placeholder="e.g., Dr. Sarah Chen, LMHC") | |
| orientation = st.text_area("Clinical Orientation", placeholder="e.g., Attachment-focused, DBT-informed, IFS, somatic...", height=80) | |
| st.markdown("<br>", unsafe_allow_html=True) | |
| if st.form_submit_button("Enter Practice Fields", use_container_width=True): | |
| if name: | |
| st.session_state.clinician_name = name | |
| st.session_state.clinician_orientation = orientation | |
| st.session_state.onboarded = True | |
| st.rerun() | |
| else: | |
| st.error("Please enter your name.") | |
| else: | |
| st.markdown(""" | |
| <div class="hub-header"> | |
| <div class="hub-logo">π±</div> | |
| <div class="hub-title">Practice Fields</div> | |
| <div class="hub-tagline">Structured practice for relational growth</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown(f""" | |
| <div class="clinician-badge"> | |
| <div class="clinician-name">{st.session_state.clinician_name}</div> | |
| <div class="clinician-orientation">{st.session_state.clinician_orientation or "Clinical Practice"}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| col1, col2, col3 = st.columns([1, 1, 1]) | |
| with col2: | |
| if st.button("β Edit Info", use_container_width=True): | |
| st.session_state.onboarded = False | |
| st.rerun() | |
| st.markdown('<p class="section-header">Practice Spaces</p>', unsafe_allow_html=True) | |
| col1, col2 = st.columns(2, gap="medium") | |
| for i, s in enumerate(SPACES): | |
| with col1 if i % 2 == 0 else col2: | |
| st.markdown(f""" | |
| <a href="{s["url"]}" target="_blank" class="field-card"> | |
| {img_tag(s["key"])} | |
| <div class="field-title">{s["title"]}</div> | |
| <div class="field-desc">{s["desc"]}</div> | |
| </a> | |
| """, unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div class="footer-container"> | |
| <div class="footer-main"> | |
| Not therapy. Not a replacement for human connection.<br> | |
| Practice spaces designed to complement work with your therapist. | |
| </div> | |
| <div class="crisis-resources"> | |
| <span class="crisis-item">988</span> | |
| <span class="crisis-item">741741</span> | |
| <span class="crisis-item">911</span> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |