File size: 7,238 Bytes
2a7e46f
19e6a20
 
 
 
 
b083143
19e6a20
11122d0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2a7e46f
 
19e6a20
11122d0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19e6a20
2a7e46f
 
 
 
 
 
 
19e6a20
2a7e46f
 
 
19e6a20
2a7e46f
19e6a20
2a7e46f
 
 
 
 
19e6a20
2a7e46f
 
8ef8c72
2a7e46f
 
11122d0
19e6a20
2a7e46f
 
 
 
 
 
8ef8c72
2a7e46f
8ef8c72
2a7e46f
 
11122d0
19e6a20
2a7e46f
11122d0
 
 
 
2a7e46f
 
19e6a20
2a7e46f
 
 
 
19e6a20
2a7e46f
 
19e6a20
2a7e46f
 
 
 
 
19e6a20
2a7e46f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11122d0
 
 
2a7e46f
 
11122d0
 
2a7e46f
19e6a20
2a7e46f
11122d0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
"""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**")