Skip to content

Commit 55eaafb

Browse files
authored
Therapy polish (#255)
* /therapy-session into /therapy * Clean up * Refactoring * New command in the list * Thinking message * Tests by AI
1 parent eac2a49 commit 55eaafb

File tree

6 files changed

+261
-36
lines changed

6 files changed

+261
-36
lines changed

internal/app/command.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ type Command string
66

77
const (
88
// User commands
9-
Start Command = "/start" // Start the bot
10-
Why Command = "/why" // Tells the purpose of the bot
11-
Note Command = "/note" // Add a new note
12-
Last Command = "/last" // Get the last note
13-
Analysis Command = "/analysis" // Get the analysis of the last notes
14-
TherapySession Command = "/therapy_session" // Start a real-time therapy session
15-
Language Command = "/language" // Change the language
16-
Settings Command = "/settings" // Show the settings
17-
Help Command = "/help" // Show the help
18-
Version Command = "/version" // Show the version
9+
Start Command = "/start" // Start the bot
10+
Why Command = "/why" // Tells the purpose of the bot
11+
Note Command = "/note" // Add a new note
12+
Last Command = "/last" // Get the last note
13+
Analysis Command = "/analysis" // Get the analysis of the last notes
14+
Therapy Command = "/therapy" // Start a real-time therapy session
15+
Language Command = "/language" // Change the language
16+
Settings Command = "/settings" // Show the settings
17+
Help Command = "/help" // Show the help
18+
Version Command = "/version" // Show the version
1919

2020
// Hidden user commands
2121
MissingNote Command = "/missing_note" // Ask to put a note from the previous text

internal/app/session.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ func handleSession(session *Session) {
5959
command := session.Job.Command
6060
commandStr := string(command)
6161
// Keep therapy session as the effective last command during active window
62-
if command == None && session.User.TherapySessionEndAt != nil && now.Before(*session.User.TherapySessionEndAt) && session.Job.LastCommand == TherapySession {
63-
commandStr = string(TherapySession)
62+
if command == None && session.User.TherapySessionEndAt != nil && now.Before(*session.User.TherapySessionEndAt) && session.Job.LastCommand == Therapy {
63+
commandStr = string(Therapy)
6464
}
6565
session.User.LastCommand = &commandStr
6666
session.User.Timestamp = &now
@@ -71,7 +71,7 @@ func handleSession(session *Session) {
7171
}
7272

7373
// If another command is called while therapy is active, end therapy
74-
if command != None && command != TherapySession && session.User.TherapySessionEndAt != nil {
74+
if command != None && command != Therapy && session.User.TherapySessionEndAt != nil {
7575
endTherapySession(session)
7676
}
7777

@@ -87,7 +87,7 @@ func handleSession(session *Session) {
8787
handleWhy(session)
8888
case Note:
8989
startNote(session)
90-
case TherapySession:
90+
case Therapy:
9191
startTherapySession(session)
9292
case MissingNote:
9393
handleMissingNote(session, noteStorage)
@@ -161,7 +161,7 @@ func handleSession(session *Session) {
161161
switch session.Job.LastCommand {
162162
case Note:
163163
finishNote(*session.Job.Input, session, noteStorage)
164-
case TherapySession:
164+
case Therapy:
165165
relayTherapyMessage(*session.Job.Input, session)
166166
case Support:
167167
finishFeedback(session, feedbackStorage)
@@ -176,7 +176,7 @@ func handleSession(session *Session) {
176176
// If the user is not in typing mode but therapy session is active, keep the session alive and forward
177177
if session.User.TherapySessionEndAt != nil && now.Before(*session.User.TherapySessionEndAt) {
178178
session.User.IsTyping = true
179-
session.Job.LastCommand = TherapySession
179+
session.Job.LastCommand = Therapy
180180
relayTherapyMessage(*session.Job.Input, session)
181181
} else {
182182
handleInputWithoutCommand(session)

internal/app/therapy.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ func callTherapySessionEndpoint(text string, session *Session) *string {
6161
// Relay a user message to the therapy session backend and append the reply
6262
func relayTherapyMessage(text string, session *Session) {
6363
//coverage:ignore
64-
// Send immediate typing acknowledgement is already enabled via IsTyping
64+
// Send immediate feedback message to let user know we're processing
65+
thinkingMessageKey := getRandomThinkingMessage(session)
66+
setOutputText(thinkingMessageKey, session)
67+
68+
// Send the actual request to the therapy endpoint
6569
reply := callTherapySessionEndpoint(text, session)
6670
if reply != nil && *reply != "" {
6771
setOutputRawText(*reply, session)
@@ -250,6 +254,29 @@ func parseRunResponse(respStr string) *string {
250254
return &respStr
251255
}
252256

257+
// Get a random thinking message to show immediate feedback
258+
func getRandomThinkingMessage(session *Session) string {
259+
thinkingMessages := []string{
260+
"therapy_thinking_1",
261+
"therapy_thinking_2",
262+
"therapy_thinking_3",
263+
"therapy_thinking_4",
264+
"therapy_thinking_5",
265+
}
266+
267+
// Use a simple hash of the user ID to get consistent randomness per user
268+
userIDHash := 0
269+
for _, char := range session.User.ID {
270+
userIDHash += int(char)
271+
}
272+
273+
// Add current time to make it more random
274+
userIDHash += int(time.Now().Unix())
275+
276+
selectedIndex := userIDHash % len(thinkingMessages)
277+
return thinkingMessages[selectedIndex]
278+
}
279+
253280
// End the therapy session and notify the user
254281
func endTherapySession(session *Session) {
255282
session.User.IsTyping = false

internal/app/therapy_test.go

Lines changed: 199 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"net/http"
66
"net/http/httptest"
7+
"os"
78
"testing"
89
"time"
910

@@ -15,7 +16,7 @@ import (
1516
func TestStartTherapySession(t *testing.T) {
1617
ctx := context.Background()
1718
user := &database.User{IsTyping: false}
18-
session := createSession(&Job{Command: TherapySession}, user, nil, &ctx)
19+
session := createSession(&Job{Command: Therapy}, user, nil, &ctx)
1920

2021
startTherapySession(session)
2122

@@ -37,7 +38,7 @@ func TestEndTherapySession(t *testing.T) {
3738
ctx := context.Background()
3839
endAt := time.Now().Add(10 * time.Minute)
3940
user := &database.User{IsTyping: true, TherapySessionEndAt: &endAt}
40-
session := createSession(&Job{Command: TherapySession}, user, nil, &ctx)
41+
session := createSession(&Job{Command: Therapy}, user, nil, &ctx)
4142

4243
endTherapySession(session)
4344

@@ -89,11 +90,35 @@ func TestRelayTherapyMessage(t *testing.T) {
8990

9091
relayTherapyMessage("hi", session)
9192

92-
if len(session.Job.Output) == 0 {
93-
t.Fatalf("expected at least one output")
93+
if len(session.Job.Output) < 2 {
94+
t.Fatalf("expected at least two outputs (immediate feedback + response)")
95+
}
96+
97+
// First output should be a thinking message
98+
firstOutput := session.Job.Output[0]
99+
validThinkingKeys := []string{
100+
"therapy_thinking_1",
101+
"therapy_thinking_2",
102+
"therapy_thinking_3",
103+
"therapy_thinking_4",
104+
"therapy_thinking_5",
105+
}
106+
107+
found := false
108+
for _, validKey := range validThinkingKeys {
109+
if firstOutput.TextID == validKey {
110+
found = true
111+
break
112+
}
113+
}
114+
115+
if !found {
116+
t.Fatalf("expected first output to be a thinking message, got %s", firstOutput.TextID)
94117
}
95-
if session.Job.Output[0].TextID != "Hello, I'm here for you." {
96-
t.Fatalf("unexpected relay text: %s", session.Job.Output[0].TextID)
118+
119+
// Second output should be the actual therapy response
120+
if session.Job.Output[1].TextID != "Hello, I'm here for you." {
121+
t.Fatalf("unexpected relay text: %s", session.Job.Output[1].TextID)
97122
}
98123
}
99124

@@ -143,13 +168,40 @@ func TestHandleSession_ForwardDuringActive(t *testing.T) {
143168
locale := "en"
144169
user := &database.User{ID: "u1", TherapySessionEndAt: &future, IsTyping: true, Locale: &locale}
145170
input := "some text"
146-
job := &Job{Command: None, LastCommand: TherapySession, Input: &input}
171+
job := &Job{Command: None, LastCommand: Therapy, Input: &input}
147172
session := createSession(job, user, nil, &ctx)
148173

149174
handleSession(session)
150175

151-
if len(session.Job.Output) == 0 || session.Job.Output[0].TextID != "Therapist reply" {
152-
t.Fatalf("expected Therapist reply, got %v", session.Job.Output)
176+
if len(session.Job.Output) < 2 {
177+
t.Fatalf("expected at least two outputs (immediate feedback + response), got %v", session.Job.Output)
178+
}
179+
180+
// First output should be a thinking message
181+
firstOutput := session.Job.Output[0]
182+
validThinkingKeys := []string{
183+
"therapy_thinking_1",
184+
"therapy_thinking_2",
185+
"therapy_thinking_3",
186+
"therapy_thinking_4",
187+
"therapy_thinking_5",
188+
}
189+
190+
found := false
191+
for _, validKey := range validThinkingKeys {
192+
if firstOutput.TextID == validKey {
193+
found = true
194+
break
195+
}
196+
}
197+
198+
if !found {
199+
t.Fatalf("expected first output to be a thinking message, got %s", firstOutput.TextID)
200+
}
201+
202+
// Second output should be the actual therapy response
203+
if session.Job.Output[1].TextID != "Therapist reply" {
204+
t.Fatalf("expected Therapist reply in second output, got %v", session.Job.Output)
153205
}
154206
}
155207

@@ -187,11 +239,35 @@ func TestRelayTherapyMessage_ExistingSessionContinues(t *testing.T) {
187239

188240
relayTherapyMessage("hi", session)
189241

190-
if len(session.Job.Output) == 0 {
191-
t.Fatalf("expected at least one output")
242+
if len(session.Job.Output) < 2 {
243+
t.Fatalf("expected at least two outputs (immediate feedback + response)")
192244
}
193-
if session.Job.Output[0].TextID != "Hello again" {
194-
t.Fatalf("unexpected relay text: %s", session.Job.Output[0].TextID)
245+
246+
// First output should be a thinking message
247+
firstOutput := session.Job.Output[0]
248+
validThinkingKeys := []string{
249+
"therapy_thinking_1",
250+
"therapy_thinking_2",
251+
"therapy_thinking_3",
252+
"therapy_thinking_4",
253+
"therapy_thinking_5",
254+
}
255+
256+
found := false
257+
for _, validKey := range validThinkingKeys {
258+
if firstOutput.TextID == validKey {
259+
found = true
260+
break
261+
}
262+
}
263+
264+
if !found {
265+
t.Fatalf("expected first output to be a thinking message, got %s", firstOutput.TextID)
266+
}
267+
268+
// Second output should be the actual therapy response
269+
if session.Job.Output[1].TextID != "Hello again" {
270+
t.Fatalf("unexpected relay text: %s", session.Job.Output[1].TextID)
195271
}
196272
}
197273

@@ -215,3 +291,113 @@ func TestHandleSession_EndOnOtherCommand(t *testing.T) {
215291
t.Fatalf("expected commands_hint second, got %s", session.Job.Output[1].TextID)
216292
}
217293
}
294+
295+
func TestGetRandomThinkingMessage(t *testing.T) {
296+
ctx := context.Background()
297+
user := &database.User{ID: "test_user_123"}
298+
session := createSession(&Job{Command: Therapy}, user, nil, &ctx)
299+
300+
// Test that we get a valid thinking message key
301+
messageKey := getRandomThinkingMessage(session)
302+
303+
validKeys := []string{
304+
"therapy_thinking_1",
305+
"therapy_thinking_2",
306+
"therapy_thinking_3",
307+
"therapy_thinking_4",
308+
"therapy_thinking_5",
309+
}
310+
311+
found := false
312+
for _, validKey := range validKeys {
313+
if messageKey == validKey {
314+
found = true
315+
break
316+
}
317+
}
318+
319+
if !found {
320+
t.Fatalf("expected one of %v, got %s", validKeys, messageKey)
321+
}
322+
}
323+
324+
func TestRelayTherapyMessageWithImmediateFeedback(t *testing.T) {
325+
// Create a mock server that simulates the therapy endpoint
326+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
327+
// Simulate a successful therapy response
328+
response := `{"content": {"parts": [{"text": "I understand your concerns. Let's work through this together."}]}}`
329+
w.Header().Set("Content-Type", "application/json")
330+
w.WriteHeader(http.StatusOK)
331+
w.Write([]byte(response))
332+
}))
333+
defer server.Close()
334+
335+
// Set environment variables for testing
336+
originalURL := os.Getenv("CAPY_THERAPY_SESSION_URL")
337+
originalCloud := os.Getenv("CLOUD")
338+
originalToken := os.Getenv("CAPY_AGENT_TOKEN")
339+
340+
os.Setenv("CAPY_THERAPY_SESSION_URL", server.URL)
341+
os.Setenv("CLOUD", "false")
342+
os.Setenv("CAPY_AGENT_TOKEN", "test_token")
343+
344+
defer func() {
345+
os.Setenv("CAPY_THERAPY_SESSION_URL", originalURL)
346+
os.Setenv("CLOUD", originalCloud)
347+
os.Setenv("CAPY_AGENT_TOKEN", originalToken)
348+
}()
349+
350+
ctx := context.Background()
351+
user := &database.User{
352+
ID: "test_user",
353+
TherapySessionId: stringPtr("test_session_id"),
354+
TherapySessionEndAt: timePtr(time.Now().Add(30 * time.Minute)),
355+
IsTyping: true,
356+
}
357+
session := createSession(&Job{Command: Therapy}, user, nil, &ctx)
358+
359+
// Call relayTherapyMessage
360+
relayTherapyMessage("I'm feeling anxious about work", session)
361+
362+
// Check that we got at least 2 outputs: immediate feedback + actual response
363+
if len(session.Job.Output) < 2 {
364+
t.Fatalf("expected at least 2 outputs, got %d", len(session.Job.Output))
365+
}
366+
367+
// Check that the first output is a thinking message
368+
firstOutput := session.Job.Output[0]
369+
validThinkingKeys := []string{
370+
"therapy_thinking_1",
371+
"therapy_thinking_2",
372+
"therapy_thinking_3",
373+
"therapy_thinking_4",
374+
"therapy_thinking_5",
375+
}
376+
377+
found := false
378+
for _, validKey := range validThinkingKeys {
379+
if firstOutput.TextID == validKey {
380+
found = true
381+
break
382+
}
383+
}
384+
385+
if !found {
386+
t.Fatalf("expected first output to be a thinking message, got %s", firstOutput.TextID)
387+
}
388+
389+
// Check that the second output is the actual therapy response
390+
secondOutput := session.Job.Output[1]
391+
if !strings.Contains(secondOutput.TextID, "I understand your concerns") {
392+
t.Fatalf("expected therapy response in second output TextID, got: %s", secondOutput.TextID)
393+
}
394+
}
395+
396+
// Helper functions for creating test data
397+
func stringPtr(s string) *string {
398+
return &s
399+
}
400+
401+
func timePtr(t time.Time) *time.Time {
402+
return &t
403+
}

0 commit comments

Comments
 (0)