import { useState } from "react"; const MODALITIES = [ { id: "cbt", label: "CBT", full: "Cognitive Behavioral Therapy" }, { id: "dbt", label: "DBT", full: "Dialectical Behavior Therapy" }, { id: "narrative", label: "Narrative", full: "Narrative Therapy" }, { id: "somatic", label: "Somatic", full: "Somatic Therapy" }, ]; const STYLE = ` @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=DM+Sans:wght@300;400;500&display=swap'); * { box-sizing: border-box; margin: 0; padding: 0; } body { background: #f5f0f8; min-height: 100vh; font-family: 'DM Sans', sans-serif; } .app { min-height: 100vh; background: linear-gradient(160deg, #fdf6ff 0%, #f5f0f8 50%, #eef4fb 100%); padding: 2.5rem 1.5rem; color: #2d2535; position: relative; overflow: hidden; } .app::before { content: ''; position: fixed; top: -200px; right: -200px; width: 500px; height: 500px; background: radial-gradient(circle, rgba(196,168,220,0.18) 0%, transparent 70%); pointer-events: none; } .app::after { content: ''; position: fixed; bottom: -150px; left: -150px; width: 400px; height: 400px; background: radial-gradient(circle, rgba(168,196,220,0.15) 0%, transparent 70%); pointer-events: none; } .container { max-width: 720px; margin: 0 auto; position: relative; z-index: 1; } .header { text-align: center; margin-bottom: 2.5rem; } .eyebrow { font-size: 0.65rem; letter-spacing: 0.25em; text-transform: uppercase; color: #9b7bb5; margin-bottom: 0.75rem; font-weight: 500; } h1 { font-family: 'Cormorant Garamond', serif; font-size: clamp(2rem, 5vw, 2.8rem); font-weight: 600; color: #2d2535; line-height: 1.15; margin-bottom: 0.5rem; } h1 em { font-style: italic; color: #9b7bb5; } .subtitle { font-size: 0.8rem; color: #8a7a96; font-weight: 300; letter-spacing: 0.03em; } .divider { width: 60px; height: 1px; background: linear-gradient(90deg, transparent, #c4a8d8, transparent); margin: 1.25rem auto; } .card { background: rgba(255,255,255,0.72); border: 1px solid rgba(180,155,210,0.25); border-radius: 16px; padding: 2rem; margin-bottom: 1.5rem; backdrop-filter: blur(12px); box-shadow: 0 2px 16px rgba(155,123,181,0.07); } .label { font-size: 0.65rem; letter-spacing: 0.2em; text-transform: uppercase; color: #9b7bb5; font-weight: 500; margin-bottom: 1rem; display: block; } .optional-badge { font-size: 0.58rem; letter-spacing: 0.1em; background: rgba(155,123,181,0.12); color: #9b7bb5; border-radius: 20px; padding: 0.15rem 0.5rem; margin-left: 0.5rem; text-transform: uppercase; font-weight: 400; vertical-align: middle; } .modality-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; } .modality-btn { background: rgba(255,255,255,0.6); border: 1px solid rgba(180,155,210,0.3); border-radius: 10px; padding: 0.9rem 1rem; cursor: pointer; transition: all 0.2s ease; text-align: left; color: #2d2535; } .modality-btn:hover { border-color: rgba(155,123,181,0.6); background: rgba(196,168,220,0.1); } .modality-btn.active { border-color: #9b7bb5; background: rgba(155,123,181,0.12); } .modality-btn .mod-label { font-family: 'Cormorant Garamond', serif; font-size: 1.1rem; font-weight: 600; display: block; color: #2d2535; } .modality-btn.active .mod-label { color: #7a5a9a; } .modality-btn .mod-full { font-size: 0.7rem; color: #a090b0; display: block; margin-top: 0.2rem; font-weight: 300; } textarea { width: 100%; background: rgba(255,255,255,0.6); border: 1px solid rgba(180,155,210,0.3); border-radius: 10px; padding: 1rem; color: #2d2535; font-family: 'DM Sans', sans-serif; font-size: 0.9rem; font-weight: 300; resize: vertical; min-height: 90px; outline: none; transition: border-color 0.2s; line-height: 1.6; } textarea::placeholder { color: #c0b0cc; } textarea:focus { border-color: rgba(155,123,181,0.6); background: rgba(255,255,255,0.85); } .generate-btn { width: 100%; background: linear-gradient(135deg, #b89fd4 0%, #9b7bb5 100%); border: none; border-radius: 10px; padding: 1rem 1.5rem; color: #fff; font-family: 'DM Sans', sans-serif; font-weight: 500; font-size: 0.85rem; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; gap: 0.5rem; box-shadow: 0 4px 16px rgba(155,123,181,0.25); } .generate-btn:hover:not(:disabled) { background: linear-gradient(135deg, #c8aee4 0%, #ab8bc5 100%); transform: translateY(-1px); box-shadow: 0 8px 24px rgba(155,123,181,0.3); } .generate-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none; } .spinner { width: 16px; height: 16px; border: 2px solid rgba(255,255,255,0.35); border-top-color: #fff; border-radius: 50%; animation: spin 0.7s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .result { background: rgba(255,255,255,0.75); border: 1px solid rgba(180,155,210,0.25); border-radius: 16px; overflow: hidden; margin-top: 1.5rem; animation: fadeUp 0.4s ease; box-shadow: 0 4px 24px rgba(155,123,181,0.1); } @keyframes fadeUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } .result-section { padding: 1.75rem 2rem; border-bottom: 1px solid rgba(180,155,210,0.15); } .result-section:last-child { border-bottom: none; } .section-tag { font-size: 0.6rem; letter-spacing: 0.25em; text-transform: uppercase; font-weight: 500; margin-bottom: 0.75rem; display: flex; align-items: center; gap: 0.5rem; } .section-tag::after { content: ''; flex: 1; height: 1px; background: rgba(180,155,210,0.25); } .tag-psych { color: #7aabe0; } .tag-activity { color: #9b7bb5; } .tag-script { color: #7ab5a0; } .tag-hw { color: #b07ab5; } .tag-followup { color: #c09070; } .result-section h3 { font-family: 'Cormorant Garamond', serif; font-size: 1.3rem; font-weight: 600; color: #2d2535; margin-bottom: 0.75rem; line-height: 1.3; } .result-section p, .result-section li { font-size: 0.88rem; color: #4a3d58; line-height: 1.8; font-weight: 300; } .result-section ul, .result-section ol { padding-left: 1.2rem; } .result-section li { margin-bottom: 0.4rem; } .steps-list { list-style: none; padding: 0; } .steps-list li { display: flex; gap: 1rem; margin-bottom: 1.1rem; align-items: flex-start; } .step-num { min-width: 26px; height: 26px; background: rgba(155,123,181,0.12); border: 1px solid rgba(155,123,181,0.3); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.65rem; color: #9b7bb5; font-weight: 500; margin-top: 0.1rem; flex-shrink: 0; } .step-body { flex: 1; } .step-instruction { font-size: 0.88rem; color: #4a3d58; line-height: 1.7; font-weight: 400; margin-bottom: 0.35rem; } .step-script { font-size: 0.82rem; color: #6a5a80; font-style: italic; line-height: 1.65; background: rgba(155,123,181,0.07); border-left: 2px solid rgba(155,123,181,0.3); padding: 0.4rem 0.75rem; border-radius: 0 6px 6px 0; margin-top: 0.3rem; } .script-label { font-size: 0.6rem; letter-spacing: 0.15em; text-transform: uppercase; color: #9b7bb5; font-weight: 500; font-style: normal; display: block; margin-bottom: 0.2rem; } .copy-btn { background: transparent; border: 1px solid rgba(155,123,181,0.35); border-radius: 8px; padding: 0.5rem 1rem; color: #9b7bb5; font-family: 'DM Sans', sans-serif; font-size: 0.7rem; letter-spacing: 0.1em; text-transform: uppercase; cursor: pointer; transition: all 0.2s; } .copy-btn:hover { background: rgba(155,123,181,0.1); border-color: rgba(155,123,181,0.6); } .error { background: rgba(220,100,100,0.08); border: 1px solid rgba(220,100,100,0.25); border-radius: 10px; padding: 1rem; color: #c06060; font-size: 0.85rem; margin-top: 1rem; } .footer { text-align: center; margin-top: 2.5rem; font-size: 0.65rem; color: #c0b0cc; letter-spacing: 0.1em; text-transform: uppercase; } `; function parseResult(text) { const sections = { psychoed: "", title: "", materials: "", walkthrough: [], homework: "", followup: "" }; const lines = text.split("\n"); let current = null; let currentStep = null; for (const line of lines) { const l = line.trim(); if (!l) continue; if (/^#\s*psychoed/i.test(l)) { current = "psychoed"; continue; } if (/^#\s*activity title|^#\s*title/i.test(l)) { current = "title"; continue; } if (/^#\s*material/i.test(l)) { current = "materials"; continue; } if (/^#\s*therapist walkthrough|^#\s*walkthrough|^#\s*activity steps/i.test(l)) { current = "walkthrough"; continue; } if (/^#\s*homework|^#\s*between session/i.test(l)) { current = "homework"; continue; } if (/^#\s*follow/i.test(l)) { current = "followup"; continue; } if (current === "psychoed") sections.psychoed += l + " "; else if (current === "title") { if (!sections.title) sections.title = l.replace(/^[#*\-]+/, "").trim(); } else if (current === "materials") sections.materials += l + " "; else if (current === "walkthrough") { // Detect numbered step start const stepMatch = l.match(/^(\d+)\.\s+(.+)/); if (stepMatch) { if (currentStep) sections.walkthrough.push(currentStep); currentStep = { instruction: stepMatch[2], doText: "", sayText: "" }; } else if (currentStep) { if (/^what to (do|say)|^-\s*what to (do|say)/i.test(l)) { // skip label lines } else if (/^[""]/.test(l) || /say:/i.test(l)) { currentStep.sayText += l.replace(/^say:\s*/i, "") + " "; } else { currentStep.doText += l + " "; } } } else if (current === "homework") sections.homework += l + " "; else if (current === "followup") sections.followup += l + " "; } if (currentStep) sections.walkthrough.push(currentStep); return sections; } export default function App() { const [modality, setModality] = useState(null); const [issue, setIssue] = useState(""); const [lastNote, setLastNote] = useState(""); const [treatmentPlan, setTreatmentPlan] = useState(""); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [rawText, setRawText] = useState(""); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); async function generate() { if (!modality || !issue.trim()) return; setLoading(true); setResult(null); setError(null); const selectedMod = MODALITIES.find(m => m.id === modality); const contextBlock = [ lastNote.trim() ? `Last Session Note:\n${lastNote.trim()}` : null, treatmentPlan.trim() ? `Treatment Plan / Goals:\n${treatmentPlan.trim()}` : null, ].filter(Boolean).join("\n\n"); const prompt = `You are an expert clinical social worker and trauma-informed therapist. Generate a complete, ready-to-use in-session therapeutic activity for a therapist to use RIGHT NOW with their client. Modality: ${selectedMod.full} Presenting Issue: ${issue} ${contextBlock ? `\nAdditional Clinical Context:\n${contextBlock}` : ""} Respond in this EXACT format using these exact headers. Do not add any extra headers. # Psychoeducation [2-3 warm, jargon-free sentences the therapist can say directly to the client to introduce the concept. Make it relatable, human, and grounding.] # Activity Title [A clear, approachable name — not clinical-sounding] # Materials Needed [Brief list, or "None — no materials required."] # Therapist Walkthrough [This is the most important section. Write a full step-by-step script the therapist can follow IN SESSION. Number each step. For each step write TWO parts: - WHAT TO DO: The therapist's action or instruction - WHAT TO SAY: Exact or suggested language the therapist can speak aloud to the client, in quotes. Make this warm, trauma-informed, and natural-sounding. Include pauses, check-ins, and client response prompts where appropriate. Write 5-8 steps total. Be thorough — this should feel like having a supervisor in the room guiding you.] # Homework [1-2 sentences describing a concrete between-session practice the client can do, tied directly to what was done in session.] # Follow-Up [2-3 check-in questions or prompts for the therapist to use at the top of NEXT session to debrief this activity.] Keep everything practical, trauma-informed, and immediately usable.`; try { const response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: "claude-sonnet-4-20250514", max_tokens: 1500, messages: [{ role: "user", content: prompt }] }) }); const data = await response.json(); const text = data.content?.map(b => b.text || "").join("\n") || ""; setRawText(text); setResult(parseResult(text)); } catch (e) { setError("Something went wrong generating the activity. Please try again."); } finally { setLoading(false); } } function copyAll() { navigator.clipboard.writeText(rawText); setCopied(true); setTimeout(() => setCopied(false), 2000); } const canGenerate = modality && issue.trim().length > 3 && !loading; return ( <>

The Milian Collective · Clinical Tools

In-Session Activity Generator

Select a modality, add context — get a full therapist walkthrough, ready for today's session.

01 — Select Modality
{MODALITIES.map(m => ( ))}
02 — Presenting Issue