Spaces:
Running
Running
import os | |
import streamlit as st | |
from anthropic import Anthropic | |
from datetime import datetime | |
import random | |
# Initialize page configuration - MUST BE FIRST ST COMMAND | |
st.set_page_config( | |
page_title="NurtureNest: Inner Child Mirror", | |
page_icon="🪴", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# Age context dictionary | |
AGE_CONTEXT = { | |
"3-5": { | |
"voice": "Very simple, sensory-focused reassurance", | |
"example": "Your body was so tight. That's okay. You're safe now.", | |
"description": "Simple words and gentle comfort for your littlest self", | |
"color": "#FFF5E6", | |
"border": "#FFB366" | |
}, | |
"6-8": { | |
"voice": "Gentle relational reflection", | |
"example": "Sometimes big feelings come when we feel alone. I hear how hard that was.", | |
"description": "Warm understanding for your grade school self", | |
"color": "#F0F7F4", | |
"border": "#88C6B6" | |
}, | |
"9-12": { | |
"voice": "Emotionally attuned validation", | |
"example": "It makes sense that you felt that way. Your feelings matter.", | |
"description": "Respectful reflection for your older child self", | |
"color": "#F5F8FC", | |
"border": "#9FB7E3" | |
} | |
} | |
# Handle API key setup | |
try: | |
# Try to get API key from environment or secrets | |
api_key = os.getenv("anthropic_key") | |
# Try Streamlit secrets if env var not found | |
if not api_key and hasattr(st, 'secrets'): | |
try: | |
api_key = st.secrets["anthropic_key"] | |
except (KeyError, AttributeError): | |
pass | |
if not api_key: | |
st.error(""" | |
⚠️ API key not found. Please make sure: | |
1. You've added the secret in Hugging Face Space settings | |
2. The secret is named exactly 'anthropic_key' | |
3. The Space has been rebuilt after adding the secret | |
""") | |
st.stop() | |
c = Anthropic(api_key=api_key) | |
except Exception as e: | |
st.error("Error initializing AI client. Please check your configuration.") | |
st.stop() | |
# Initialize session state | |
if 'chat_history' not in st.session_state: | |
st.session_state.chat_history = [] | |
if 'journal_entries' not in st.session_state: | |
st.session_state.journal_entries = [] | |
# Sidebar content | |
st.sidebar.markdown(""" | |
# Welcome to NurtureNest: Inner Child Mirror 🪴 | |
A gentle space for re-parenting through connection with your inner child. | |
Here you can: | |
- Connect with your younger self | |
- Process parenting moments with deep understanding | |
- Experience the healing power of being truly seen | |
- Learn to parent from a place of wholeness | |
*Created with deep respect for the healing journey of parents.* | |
--- | |
Created by [Jocelyn Skillman LMHC](http://www.jocelynskillman.com) | |
[@jocelynskillmanlmhc](https://jocelynskillmanlmhc.substack.com/) | |
""") | |
# Main content area | |
st.markdown(""" | |
# NurtureNest | |
# Welcome to the Inner Child Mirror | |
## A gentle place to be held, not fixed. | |
Parenting isn't just about raising our children. It's also about meeting the younger parts of ourselves—the ones who didn't always get the co-regulation, safety, or softness they needed. These parts don't disappear when we grow up. They often rise up when we're stressed, overwhelmed, or when our child's behavior echoes an old wound. | |
The Inner Child Mirror is a simple but powerful tool in your parenting toolkit. | |
### Here's how it works: | |
1. You write a brief note about a hard moment from today or how you are feeling right now | |
2. You choose an age (3–12) that feels connected to how you were feeling | |
3. You receive a warm, developmentally attuned reflection—as if spoken to that younger you | |
This practice is grounded in trauma-informed care, attachment science, and the work of experts like Dr. Dan Siegel, Sarah Peyton, and Dr. Becky Kennedy. It helps you: | |
- Build compassion for yourself—especially after rupture moments | |
- Expand your capacity for repair (with your child and yourself) | |
- Notice and tend to your own nervous system with care | |
- Interrupt inherited patterns with love and attunement | |
You're not just "using an app"—you're doing intergenerational work in the most tender, doable way. | |
Not by being perfect. But by practicing presence—even with the parts of you that were once overwhelmed, scared, or unseen. | |
<div class="special-note"> | |
<p><strong>This is not therapy. It's not performance. It's a ritual of repair.</strong><br> | |
You're not alone in this.</p> | |
</div> | |
When you're ready, choose an age. Let the mirror offer you the kind of reflection every child deserves. | |
--- | |
""", unsafe_allow_html=True) | |
# Update CSS to include special styling for the introduction | |
st.markdown(""" | |
<style> | |
/* Enhanced typography for introduction */ | |
.stApp { | |
background-color: #F5F5F0; | |
font-family: 'Inter', 'Nunito', sans-serif; | |
} | |
h1 { | |
color: #2E2E2E; | |
font-family: 'Nunito', sans-serif; | |
font-size: 2.5rem; | |
font-weight: 700; | |
margin-bottom: 0.5rem; | |
line-height: 1.2; | |
} | |
h2 { | |
color: #666666; | |
font-family: 'Inter', sans-serif; | |
font-size: 1.5rem; | |
font-weight: 400; | |
margin-bottom: 2rem; | |
font-style: italic; | |
} | |
h3 { | |
color: #2E2E2E; | |
font-family: 'Nunito', sans-serif; | |
font-size: 1.75rem; | |
font-weight: 600; | |
margin: 2rem 0 1rem 0; | |
} | |
p { | |
color: #2E2E2E; | |
font-family: 'Inter', sans-serif; | |
font-size: 1.1rem; | |
line-height: 1.8; | |
margin: 1rem 0; | |
} | |
ul { | |
margin: 1.5rem 0; | |
padding-left: 1.5rem; | |
} | |
li { | |
color: #2E2E2E; | |
font-family: 'Inter', sans-serif; | |
font-size: 1.1rem; | |
line-height: 1.8; | |
margin: 0.5rem 0; | |
} | |
.special-note { | |
background-color: #FFF5E6; | |
border-left: 4px solid #FFB366; | |
padding: 1.5rem; | |
border-radius: 1rem; | |
margin: 2rem 0; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.05); | |
} | |
.special-note p { | |
margin: 0; | |
font-size: 1.2rem; | |
color: #2E2E2E; | |
} | |
hr { | |
margin: 2rem 0; | |
border: none; | |
border-top: 1px solid #E0E0E0; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Age selection | |
st.markdown("### Choose the age of your inner child") | |
selected_age = st.selectbox( | |
"Age Selection", | |
options=list(AGE_CONTEXT.keys()), | |
format_func=lambda x: f"Age {x}: {AGE_CONTEXT[x]['description']}", | |
help="Select the age of your inner child that needs to be heard today", | |
label_visibility="collapsed" | |
) | |
# Display age context description | |
st.markdown(f""" | |
<div style="padding: 1rem; background-color: {AGE_CONTEXT[selected_age]['color']}; border-left: 4px solid {AGE_CONTEXT[selected_age]['border']}; border-radius: 0.5rem; margin: 1rem 0;"> | |
<p style="margin: 0; font-style: italic;">{AGE_CONTEXT[selected_age]['voice']}</p> | |
<p style="margin: 0.5rem 0 0 0; font-size: 0.9em; color: #666666;">Example: {AGE_CONTEXT[selected_age]['example']}</p> | |
</div> | |
""", unsafe_allow_html=True) | |
# Crisis support notice | |
st.markdown(""" | |
<div class="alert"> | |
<strong>⚠️ Important:</strong> This is not therapy. If you need support, please reach out to a mental health professional or click <a href="https://www.samhsa.gov/find-help/national-helpline" target="_blank">here</a> for resources. | |
</div> | |
""", unsafe_allow_html=True) | |
# Journal input and chat interface | |
st.markdown("### Share what's on your heart...") | |
col1, col2 = st.columns([4, 1]) | |
with col1: | |
journal_entry = st.text_area( | |
"Your sharing", | |
height=100, | |
key="journal_input", | |
help="Share your parenting moment. Your inner child will be heard with care.", | |
placeholder="Tell me about a moment that feels hard...", | |
label_visibility="collapsed" | |
) | |
with col2: | |
submit = st.button("Share 💝", use_container_width=True) | |
if submit and journal_entry: | |
try: | |
# Create age-appropriate system message | |
system_message = f"""You are a trauma-informed Inner Child Mirror for parents. | |
You speak directly to their {selected_age}-year-old self with deep emotional attunement. | |
Your role is to provide emotional safety and validation, never advice or correction. | |
Respond as if speaking to a {selected_age}-year-old, using: | |
- Age-appropriate language and concepts | |
- Simple, kind sentences (5-7 lines maximum) | |
- Focus on emotional and bodily experience | |
- Unconditional positive regard | |
- No fixing, explaining, or instructing | |
For age {selected_age}: | |
{AGE_CONTEXT[selected_age]['voice']} | |
Always end with a gentle reminder of worth, such as: | |
"You are still good." | |
"You didn't do anything wrong by feeling that way." | |
"You are still loved. You can try again." | |
"You're not alone." | |
""" | |
message = c.messages.create( | |
model="claude-3-opus-20240229", | |
max_tokens=1000, | |
system=system_message, | |
messages=[{"role": "user", "content": journal_entry}] | |
) | |
# Add the new exchange to chat history | |
st.session_state.chat_history.append({ | |
"user_message": journal_entry, | |
"ai_response": message.content[0].text, | |
"age": selected_age, | |
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M") | |
}) | |
# Clear the input area after sending | |
st.rerun() | |
except Exception as e: | |
st.error(f"Error getting reflection: {str(e)}") | |
# Display chat history in reverse chronological order | |
if st.session_state.chat_history: | |
st.markdown("### Your Mirror") | |
for exchange in reversed(st.session_state.chat_history): | |
# User message | |
st.markdown(f""" | |
<div class="user-message"> | |
<div class="message-meta"><em>Your sharing</em></div> | |
<div class="message-text">{exchange["user_message"]}</div> | |
</div> | |
""", unsafe_allow_html=True) | |
# Mirror response with age-specific styling | |
age_group = exchange["age"] | |
st.markdown(f""" | |
<div style="background-color: {AGE_CONTEXT[age_group]['color']}; padding: 1.5rem; border-radius: 1rem; margin: 1rem 0; border-left: 4px solid {AGE_CONTEXT[age_group]['border']}; box-shadow: 0 2px 8px rgba(0,0,0,0.05);"> | |
<div class="message-meta"> | |
<em>Your {age_group}-year-old self is heard</em> | |
</div> | |
<div class="message-text">{exchange["ai_response"]}</div> | |
</div> | |
""", unsafe_allow_html=True) | |
# Save conversation button at the bottom | |
if st.session_state.chat_history: | |
if st.button("📥 Save this conversation"): | |
st.session_state.journal_entries.extend(st.session_state.chat_history) | |
st.success("Conversation saved to your journal.") | |
# Display saved reflections if any | |
if st.session_state.journal_entries: | |
with st.expander("📔 Your Saved Reflections", expanded=False): | |
for entry in reversed(st.session_state.journal_entries): | |
timestamp = entry.get("timestamp", "") | |
age = entry.get("age", "") | |
content = entry.get("user_message", entry.get("content", "")) | |
reflection = entry.get("ai_response", entry.get("reflection", "")) | |
st.markdown(f""" | |
**{timestamp}** - *Age {age}* | |
*Your sharing:* | |
{content} | |
*Your inner child was heard:* | |
{reflection} | |
--- | |
""") | |
# Update CSS for styling | |
st.markdown(""" | |
<style> | |
/* Base styling */ | |
.stApp { | |
background-color: #F5F5F0; | |
font-family: 'Inter', 'Nunito', sans-serif; | |
} | |
/* Sidebar styling */ | |
.css-1d391kg, .css-1p05t8e, [data-testid="stSidebar"] { | |
background-color: #FFFFFF !important; | |
} | |
[data-testid="stSidebar"] .stMarkdown { | |
color: #2E2E2E; | |
} | |
[data-testid="stSidebar"] h1 { | |
color: #2E2E2E; | |
font-size: 1.8rem; | |
margin-bottom: 1rem; | |
} | |
[data-testid="stSidebar"] p, | |
[data-testid="stSidebar"] li { | |
color: #2E2E2E; | |
font-size: 1rem; | |
} | |
[data-testid="stSidebar"] a { | |
color: #2E2E2E; | |
text-decoration: underline; | |
} | |
[data-testid="stSidebar"] a:hover { | |
color: #000000; | |
} | |
/* User message styling */ | |
.user-message { | |
background-color: #FFFFFF; | |
padding: 1.5rem; | |
border-radius: 1rem; | |
margin: 1rem 0; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.05); | |
} | |
/* Text styling */ | |
.message-text { | |
font-size: 18px; | |
line-height: 1.8; | |
color: #2E2E2E; | |
font-family: 'Nunito', 'Inter', sans-serif; | |
} | |
.message-meta { | |
font-size: 14px; | |
color: #666666; | |
margin-bottom: 0.75rem; | |
font-family: 'Inter', sans-serif; | |
} | |
/* Button styling */ | |
.stButton > button { | |
background-color: #88C6B6; | |
color: white; | |
border: none; | |
padding: 0.75rem 1.5rem; | |
border-radius: 1rem; | |
font-family: 'Nunito', 'Inter', sans-serif; | |
font-weight: 600; | |
transition: all 0.3s ease; | |
} | |
.stButton > button:hover { | |
background-color: #7AB3A3; | |
transform: translateY(-1px); | |
box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
} | |
/* Alert styling */ | |
.alert { | |
background-color: #FFF8E1; | |
border-left: 4px solid #FFB366; | |
padding: 1.5rem; | |
border-radius: 1rem; | |
margin: 1.5rem 0; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.05); | |
color: #2E2E2E; | |
} | |
.alert strong { | |
color: #2E2E2E; | |
} | |
.alert a { | |
color: #2E2E2E; | |
text-decoration: underline; | |
} | |
.alert a:hover { | |
color: #000000; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Footer | |
st.markdown("---") | |
st.markdown( | |
"Created by [Jocelyn Skillman LMHC](http://www.jocelynskillman.com) | " | |
"Learn more: [@jocelynskillmanlmhc](https://jocelynskillmanlmhc.substack.com/)" | |
) | |
st.markdown(""" | |
<div style='text-align: center; margin-top: 10px;'> | |
<p style='font-size: 0.8em; color: #666666;'>I hope to move into more full time work at this intersection but in the meantime if you found this tool helpful feel free to buy me a coffee ☕️</p> | |
<a href='https://buymeacoffee.com/JocelynSkillman' target='_blank'> | |
<img src='https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png' alt='Buy Me A Coffee' style='height: 30px !important;width: 109px !important;'> | |
</a> | |
<p style='font-size: 0.8em; color: #666666;'>Thank you so much!!!! XO</p> | |
</div> | |
""", unsafe_allow_html=True) |