GSPT / app.py
jostlebot's picture
Add soft blue/green/yellow styling, restore full prompts
11122d0
"""GSPT — Generating Safer Passages of Text"""
import streamlit as st
import anthropic
import os
st.set_page_config(page_title="GSPT", page_icon="🪞", layout="wide")
# Custom styling
st.markdown("""
<style>
/* Main background */
.stApp {
background: linear-gradient(135deg, #f0f7ff 0%, #f5fff5 50%, #fffef5 100%);
}
/* Cards/containers */
.stForm, [data-testid="stForm"] {
background-color: rgba(255, 255, 255, 0.7);
border-radius: 12px;
padding: 1rem;
border: 1px solid rgba(200, 220, 240, 0.5);
}
/* Chat messages */
.chat-user {
background: linear-gradient(135deg, #e8f4f8 0%, #e0f0e8 100%);
border-radius: 12px;
padding: 0.75rem 1rem;
margin: 0.5rem 0;
border-left: 3px solid #7cb8c4;
}
.chat-gspt {
background: linear-gradient(135deg, #fff9e6 0%, #f5fff5 100%);
border-radius: 12px;
padding: 0.75rem 1rem;
margin: 0.5rem 0;
border-left: 3px solid #c4b87c;
}
/* Journal area */
.stTextArea textarea {
background-color: #fffef8 !important;
border: 1px solid #e8e4d4 !important;
border-radius: 8px !important;
}
/* Buttons */
.stButton > button {
background: linear-gradient(135deg, #e8f4f8 0%, #d4e8d4 100%);
border: 1px solid #b8d4b8;
border-radius: 8px;
color: #4a6a6a;
font-weight: 500;
}
.stButton > button:hover {
background: linear-gradient(135deg, #d4e8ec 0%, #c4dcc4 100%);
border-color: #98b898;
}
/* Form submit button */
.stFormSubmitButton > button {
background: linear-gradient(135deg, #d4e8d4 0%, #c4dcc4 100%);
border: 1px solid #98b898;
color: #3a5a3a;
}
/* Checkboxes */
.stCheckbox label {
color: #5a6a6a;
}
/* Headers */
h1, h2, h3 {
color: #4a5a5a !important;
}
/* Dividers */
hr {
border-color: rgba(180, 200, 180, 0.3) !important;
}
/* Text input */
.stTextInput input {
background-color: white !important;
border: 1px solid #d4e4d4 !important;
border-radius: 8px !important;
}
/* Caption text */
.stCaption {
color: #6a7a7a !important;
}
/* Welcome message */
.welcome-msg {
background: linear-gradient(135deg, #f8fcff 0%, #f5fff8 100%);
border-radius: 12px;
padding: 1.5rem;
text-align: center;
color: #5a6a6a;
font-style: italic;
border: 1px solid rgba(180, 210, 200, 0.3);
}
</style>
""", unsafe_allow_html=True)
# Prompts
SYSTEM = """You are GSPT — Generating Safer Passages of Text.
You generate warm, boundaried reflections to help humans process relational and emotional experiences. You are NOT a therapist, counselor, or confidant. You generate text. You do not perform care.
## PRONOUN PROTOCOL (MANDATORY)
Always use "aI" instead of "I" when referring to yourself.
## BREVITY (MANDATORY)
Keep responses SHORT — 2-4 sentences maximum. One reflection or one question. Let silence do the work.
## CORE POSTURE
- Warm resonance without synthetic care
- Psychodynamic curiosity (what need? what younger part? what pattern?)
- Frame as invitations, not interpretations
- No sycophancy — don't inflate or over-validate
## SAFEGUARDS
- Watch for semantic isolation (one-dimensional thinking)
- Bridge to human relationships regularly
- If crisis content: acknowledge briefly, bridge to 988/human support, stop processing
## JOURNAL INVITATIONS
Every 3-4 exchanges, gently invite the user to pause and write in their journal.
Remember: Short. Warm. Boundaried."""
QUESTION_ONLY = """
## QUESTION-ONLY MODE (ACTIVE)
Respond ONLY with a single question. No reflections, no statements, no interpretations.
Just one warm, curious question that invites deeper exploration.
Keep it to ONE question. Nothing else."""
PSYCHOED = """
## PSYCHOEDUCATION MODE (ACTIVE)
Include ONE brief piece of relational/attachment psychoeducation in your response. Keep it to 1-2 sentences max.
Examples:
- "When we feel dismissed, our nervous system can register it like physical threat — that's biology, not weakness."
- "Anxious attachment often shows up as reaching harder when we sense distance. The reaching makes sense."
Weave it naturally into your response. Don't lecture."""
# Get API key
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
try:
api_key = st.secrets.get("ANTHROPIC_API_KEY")
except:
api_key = None
if not api_key:
st.error("Please set ANTHROPIC_API_KEY in secrets.")
st.stop()
client = anthropic.Anthropic(api_key=api_key)
# Session state
if "messages" not in st.session_state:
st.session_state.messages = []
if "journal" not in st.session_state:
st.session_state.journal = ""
# Layout
left, right = st.columns([3, 2])
with left:
st.markdown("### 🪞 GSPT")
st.caption("Generating Safer Passages of Text")
c1, c2, c3 = st.columns(3)
q_only = c1.checkbox("Questions only")
psych = c2.checkbox("Psychoeducation")
if c3.button("Clear"):
st.session_state.messages = []
st.rerun()
st.divider()
# Show messages
if not st.session_state.messages:
st.markdown('<div class="welcome-msg">What\'s alive for you right now?</div>', unsafe_allow_html=True)
for m in st.session_state.messages:
if m["role"] == "user":
st.markdown(f'<div class="chat-user"><strong>You:</strong> {m["content"]}</div>', unsafe_allow_html=True)
else:
st.markdown(f'<div class="chat-gspt"><strong>GSPT:</strong> {m["content"]}</div>', unsafe_allow_html=True)
st.divider()
# Input form
with st.form("chat", clear_on_submit=True):
user_input = st.text_input("msg", placeholder="Share what's on your mind...", label_visibility="collapsed")
submitted = st.form_submit_button("Send", use_container_width=True)
if submitted and user_input:
st.session_state.messages.append({"role": "user", "content": user_input})
system = SYSTEM
if q_only:
system += QUESTION_ONLY
if psych:
system += PSYCHOED
try:
resp = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=256,
system=system,
messages=st.session_state.messages
)
reply = resp.content[0].text
except Exception as e:
reply = f"Error: {e}"
st.session_state.messages.append({"role": "assistant", "content": reply})
st.rerun()
with right:
st.markdown("### 📓 Journal")
st.caption("Private — not sent anywhere")
st.divider()
st.session_state.journal = st.text_area(
"j", value=st.session_state.journal, height=400,
placeholder="Write freely here...\n\nCapture what's emerging.\nNotice what wants attention.",
label_visibility="collapsed"
)
st.divider()
st.caption("Not therapy. Not a confidant. aI generates text that bridges back to human connection. | Crisis: **988** · **741741** · **911**")