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
03 — Treatment Plan / Goals
Optional
04 — Last Session Note
Optional
{error && {error}
}
{result && (
{result.psychoed && (
)}
{result.title && (
)}
{result.walkthrough.length > 0 && (
)}
{result.homework && (
)}
{result.followup && (
)}
)}
Psychoeducation
{result.psychoed.trim()}
Activity
{result.title}
{result.materials && (Materials: {result.materials.trim()}
)}Therapist Walkthrough
-
{result.walkthrough.map((step, i) => (
-
{i + 1}{step.instruction}{step.doText ? " " + step.doText.trim() : ""}{step.sayText && (Say to client {step.sayText.trim()})}
))}
Between-Session Practice
{result.homework.trim()}
Next Session Follow-Up
{result.followup.trim()}
Dr. Seida Hood, LCSW · For Clinical Use Only