From a3cd83acd70c5d1b092b080a6c036f03a67f8e49 Mon Sep 17 00:00:00 2001 From: Maksym Bilan Date: Mon, 16 Sep 2024 15:04:23 +0300 Subject: [PATCH] AI Prompt improvements (#32) * prompts json * prompts improvements * clean up * Fixing texts * Fixing texts * tests * bug fixes * fixing ux * fixing unit tests * refactoring * one database connection per session * clean up * fixing issues --------- Co-authored-by: Maksym Bilan <> --- internal/analysis/analysis.go | 23 +++++++++++++---------- internal/analysis/client.go | 3 +-- internal/bot/analysis.go | 2 +- internal/bot/bot.go | 16 +++++++++++++--- internal/bot/database.go | 19 ------------------- internal/bot/notes.go | 16 +++------------- internal/bot/session.go | 16 ++++++++++------ internal/bot/user.go | 18 ++++++------------ internal/bot/user_test.go | 7 +++++-- internal/firestore/firestore.go | 23 ++++++++++++++++++++--- internal/firestore/note.go | 12 ++++++------ internal/firestore/user.go | 12 ++++++------ internal/scheduler/database.go | 18 ------------------ internal/scheduler/scheduler.go | 22 ++++++++++++---------- internal/scheduler/tasks.go | 17 ++++++++++++----- internal/translator/translations.go | 17 +++++++++++++---- internal/translator/translator.go | 26 +++++++++++++++++++------- internal/translator/translator_test.go | 17 ++++++++++++++++- 18 files changed, 156 insertions(+), 128 deletions(-) delete mode 100644 internal/bot/database.go delete mode 100644 internal/scheduler/database.go diff --git a/internal/analysis/analysis.go b/internal/analysis/analysis.go index 5e50225..e6b74de 100644 --- a/internal/analysis/analysis.go +++ b/internal/analysis/analysis.go @@ -11,13 +11,13 @@ import ( ) // Reqeust an analysis of the user's journal entries -func Request(notes []string, locale translator.Locale) *string { - ctx := context.Background() - client := createClient(ctx) +func Request(notes []string, locale translator.Locale, ctx *context.Context, header *string) *string { + ai := createAI() - prompt := translator.Translate(locale, "ai_analysis_prompt") + systemPrompt := translator.Prompt(locale, "ai_analysis_system_message") + userPrompt := translator.Prompt(locale, "ai_analysis_user_message") for index, note := range notes { - prompt += fmt.Sprintf("%d. %s ", index+1, note) + userPrompt += fmt.Sprintf("%d. %s ", index+1, note) } var responseSchema = generateSchema[Response]() @@ -29,9 +29,10 @@ func Request(notes []string, locale translator.Locale) *string { Strict: openai.Bool(true), } - chat, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{ + chat, err := ai.Chat.Completions.New(*ctx, openai.ChatCompletionNewParams{ Messages: openai.F([]openai.ChatCompletionMessageParamUnion{ - openai.UserMessage(prompt), + openai.SystemMessage(systemPrompt), + openai.UserMessage(userPrompt), }), ResponseFormat: openai.F[openai.ChatCompletionNewParamsResponseFormatUnion]( openai.ResponseFormatJSONSchemaParam{ @@ -39,8 +40,6 @@ func Request(notes []string, locale translator.Locale) *string { JSONSchema: openai.F(schemaParam), }, ), - // only certain models can perform structured outputs - // Model: openai.F(openai.ChatModelGPT4o2024_08_06), Model: openai.F(openai.ChatModelGPT4oMini), }) @@ -58,7 +57,11 @@ func Request(notes []string, locale translator.Locale) *string { var analysis string if response.Text != "" { - analysis = fmt.Sprintf("%s%s", translator.Translate(locale, "weekly_analysis"), response.Text) + if header != nil { + analysis = fmt.Sprintf("%s%s", translator.Translate(locale, *header), response.Text) + } else { + analysis = response.Text + } return &analysis } else { return nil diff --git a/internal/analysis/client.go b/internal/analysis/client.go index 433770e..5b102ca 100644 --- a/internal/analysis/client.go +++ b/internal/analysis/client.go @@ -1,7 +1,6 @@ package analysis import ( - "context" "os" "github.com/invopop/jsonschema" @@ -10,7 +9,7 @@ import ( ) // Create a client for the OpenAI API -func createClient(ctx context.Context) *openai.Client { +func createAI() *openai.Client { client := openai.NewClient( option.WithAPIKey(os.Getenv("CAPY_AI_KEY")), ) diff --git a/internal/bot/analysis.go b/internal/bot/analysis.go index 38b5fcd..f2a8c66 100644 --- a/internal/bot/analysis.go +++ b/internal/bot/analysis.go @@ -21,7 +21,7 @@ func handleAnalysis(session *Session) { sendMessage("analysis_waiting", session) // Request the analysis - analysis := analysis.Request(strings, session.Locale()) + analysis := analysis.Request(strings, session.Locale(), session.Context, nil) if analysis != nil { // Send the analysis setOutputText(*analysis, session) diff --git a/internal/bot/bot.go b/internal/bot/bot.go index b97451c..fb84835 100644 --- a/internal/bot/bot.go +++ b/internal/bot/bot.go @@ -1,9 +1,11 @@ package bot import ( + "context" "log" "net/http" + "github.com/capymind/internal/firestore" "github.com/capymind/internal/telegram" ) @@ -15,15 +17,21 @@ func Parse(w http.ResponseWriter, r *http.Request) { return } + // Create a context + ctx := context.Background() + + // Creat a database connection + firestore.CreateClient(&ctx) + // Create a user - user := createUser(*update) + user := createUser(*update, &ctx) if user == nil { log.Printf("[Bot] No user to process: message_id=%d", update.Message.ID) return } // Update the user's data in the database if necessary - updatedUser := updateUser(user) + updatedUser := updateUser(user, &ctx) // Create a job job := createJob(*update) @@ -33,9 +41,11 @@ func Parse(w http.ResponseWriter, r *http.Request) { } // Create and start a session - session := createSession(job, updatedUser) + session := createSession(job, updatedUser, &ctx) // Execute the job handleSession(session) // Send the response finishSession(session) + // Close the database connection + firestore.CloseClient() } diff --git a/internal/bot/database.go b/internal/bot/database.go deleted file mode 100644 index 74ebb08..0000000 --- a/internal/bot/database.go +++ /dev/null @@ -1,19 +0,0 @@ -package bot - -import ( - "context" - "log" - - google "cloud.google.com/go/firestore" - "github.com/capymind/internal/firestore" -) - -// Create Firestore client -func createClient() (*google.Client, context.Context) { - ctx := context.Background() - var client, err = firestore.NewClient(ctx) - if err != nil { - log.Printf("[Database] Error creating firestore client, %s", err.Error()) - } - return client, ctx -} diff --git a/internal/bot/notes.go b/internal/bot/notes.go index 65b3fdd..d71bcfc 100644 --- a/internal/bot/notes.go +++ b/internal/bot/notes.go @@ -23,11 +23,8 @@ func finishNote(session *Session) { // Handle the note command func handleLastNote(session *Session) { - client, ctx := createClient() - defer client.Close() - userID := session.User.ID - note, err := firestore.LastNote(ctx, client, userID) + note, err := firestore.LastNote(session.Context, userID) if err != nil { log.Printf("[Bot] Error getting last note from firestore, %s", err.Error()) } @@ -46,10 +43,6 @@ func handleLastNote(session *Session) { // Save a note func saveNote(text string, session *Session) { - // Setup the database connection - client, ctx := createClient() - defer client.Close() - // Note data timestamp := time.Now() var note = firestore.Note{ @@ -59,7 +52,7 @@ func saveNote(text string, session *Session) { } // Save the note - err := firestore.NewNote(ctx, client, *session.User, note) + err := firestore.NewNote(session.Context, *session.User, note) if err != nil { log.Printf("[Bot] Error saving note in firestore, %s", err.Error()) } @@ -67,10 +60,7 @@ func saveNote(text string, session *Session) { // Get the user's notes func getNotes(session *Session) []firestore.Note { - client, ctx := createClient() - defer client.Close() - - notes, err := firestore.GetNotes(ctx, client, session.User.ID) + notes, err := firestore.GetNotes(session.Context, session.User.ID) if err != nil { log.Printf("[Bot] Error getting notes from firestore, %s", err.Error()) } diff --git a/internal/bot/session.go b/internal/bot/session.go index f50416a..3670bd6 100644 --- a/internal/bot/session.go +++ b/internal/bot/session.go @@ -1,13 +1,16 @@ package bot import ( + "context" + "github.com/capymind/internal/firestore" "github.com/capymind/internal/translator" ) type Session struct { - Job *Job - User *firestore.User + Job *Job + User *firestore.User + Context *context.Context } // Return the locale of the current user @@ -20,14 +23,15 @@ func (session *Session) Locale() translator.Locale { // Save the user's data func (session *Session) SaveUser() { - saveUser(session.User) + saveUser(session.User, session.Context) } // Create a session -func createSession(job *Job, user *firestore.User) *Session { +func createSession(job *Job, user *firestore.User, context *context.Context) *Session { session := Session{ - Job: job, - User: user, + Job: job, + User: user, + Context: context, } return &session } diff --git a/internal/bot/user.go b/internal/bot/user.go index c0b583e..eb7fcc2 100644 --- a/internal/bot/user.go +++ b/internal/bot/user.go @@ -1,6 +1,7 @@ package bot import ( + "context" "log" "github.com/capymind/internal/firestore" @@ -9,7 +10,7 @@ import ( ) // Create a user from an update -func createUser(update telegram.Update) *firestore.User { +func createUser(update telegram.Update, ctx *context.Context) *firestore.User { var chatID int64 var telegramUser *telegram.User @@ -43,17 +44,13 @@ func createUser(update telegram.Update) *firestore.User { } // Update the user's data in the database if necessary -func updateUser(user *firestore.User) *firestore.User { +func updateUser(user *firestore.User, ctx *context.Context) *firestore.User { if user == nil { return nil } - // Setup the database connection - client, ctx := createClient() - defer client.Close() - // Check if the user exists - fetchedUser, err := firestore.GetUser(ctx, client, user.ID) + fetchedUser, err := firestore.GetUser(ctx, user.ID) if err != nil { log.Printf("[User] Error fetching user from firestore, %s", err.Error()) @@ -81,11 +78,8 @@ func updateUser(user *firestore.User) *firestore.User { } // Save a user to the database -func saveUser(user *firestore.User) { - client, ctx := createClient() - defer client.Close() - - err := firestore.SaveUser(ctx, client, *user) +func saveUser(user *firestore.User, ctx *context.Context) { + err := firestore.SaveUser(ctx, *user) if err != nil { log.Printf("[User] Error saving user to firestore, %s", err.Error()) } diff --git a/internal/bot/user_test.go b/internal/bot/user_test.go index c3052e6..3c71c4d 100644 --- a/internal/bot/user_test.go +++ b/internal/bot/user_test.go @@ -1,6 +1,7 @@ package bot import ( + "context" "testing" "github.com/capymind/internal/telegram" @@ -25,7 +26,8 @@ func TestCreateUserFromMessage(t *testing.T) { }, } - user := createUser(update) + ctx := context.Background() + user := createUser(update, &ctx) if user == nil { t.Fatalf("User is nil") } @@ -71,7 +73,8 @@ func TestUserFromCallback(t *testing.T) { }, } - user := createUser(update) + ctx := context.Background() + user := createUser(update, &ctx) if user == nil { t.Fatalf("User is nil") } diff --git a/internal/firestore/firestore.go b/internal/firestore/firestore.go index dfd7307..3d6b8cd 100644 --- a/internal/firestore/firestore.go +++ b/internal/firestore/firestore.go @@ -2,12 +2,15 @@ package firestore import ( "context" + "log" "os" "cloud.google.com/go/firestore" "google.golang.org/api/option" ) +var client *firestore.Client + // Path to the credentials file func credentialsPath() string { var path = "credentials.json" @@ -20,16 +23,16 @@ func credentialsPath() string { } // Client for Firestore -func NewClient(ctx context.Context) (*firestore.Client, error) { +func newClient(ctx *context.Context) (*firestore.Client, error) { projectID := os.Getenv("CAPY_PROJECT_ID") var client *firestore.Client var err error if os.Getenv("CLOUD") == "true" { - client, err = firestore.NewClient(ctx, projectID) + client, err = firestore.NewClient(*ctx, projectID) } else { path := credentialsPath() - client, err = firestore.NewClient(ctx, projectID, option.WithCredentialsFile(path)) + client, err = firestore.NewClient(*ctx, projectID, option.WithCredentialsFile(path)) } if err != nil { @@ -38,3 +41,17 @@ func NewClient(ctx context.Context) (*firestore.Client, error) { return client, nil } + +// Create a new Firestore database connection +func CreateClient(ctx *context.Context) { + newClient, err := newClient(ctx) + if err != nil { + log.Printf("[Firestore] Error creating firestore client, %s", err.Error()) + } + client = newClient +} + +// Close the Firestore database connection +func CloseClient() { + client.Close() +} diff --git a/internal/firestore/note.go b/internal/firestore/note.go index c5d7919..99011b4 100644 --- a/internal/firestore/note.go +++ b/internal/firestore/note.go @@ -15,9 +15,9 @@ type Note struct { } // Create a new note -func NewNote(ctx context.Context, client *firestore.Client, user User, note Note) error { +func NewNote(ctx *context.Context, user User, note Note) error { userRef := client.Collection(users.String()).Doc(user.ID) - _, _, err := client.Collection(notes.String()).Add(ctx, map[string]interface{}{ + _, _, err := client.Collection(notes.String()).Add(*ctx, map[string]interface{}{ "text": note.Text, "timestamp": note.Timestamp, "user": userRef, @@ -26,11 +26,11 @@ func NewNote(ctx context.Context, client *firestore.Client, user User, note Note } // Get the last note -func LastNote(ctx context.Context, client *firestore.Client, userID string) (*Note, error) { +func LastNote(ctx *context.Context, userID string) (*Note, error) { userRef := client.Collection(users.String()).Doc(userID) query := client.Collection(notes.String()).OrderBy("timestamp", firestore.Desc).Where("user", "==", userRef).Limit(1) - docs, err := query.Documents(ctx).GetAll() + docs, err := query.Documents(*ctx).GetAll() if err != nil { return nil, err } @@ -44,11 +44,11 @@ func LastNote(ctx context.Context, client *firestore.Client, userID string) (*No } // Get all notes -func GetNotes(ctx context.Context, client *firestore.Client, userID string) ([]Note, error) { +func GetNotes(ctx *context.Context, userID string) ([]Note, error) { userRef := client.Collection(users.String()).Doc(userID) query := client.Collection(notes.String()).OrderBy("timestamp", firestore.Desc).Where("user", "==", userRef).Limit(35) // Suppose that the user posts 5 notes per day max by 7 days - docs, err := query.Documents(ctx).GetAll() + docs, err := query.Documents(*ctx).GetAll() if err != nil { return nil, err } diff --git a/internal/firestore/user.go b/internal/firestore/user.go index 6806c67..4e5a6cd 100644 --- a/internal/firestore/user.go +++ b/internal/firestore/user.go @@ -20,8 +20,8 @@ type User struct { } // Get a user from the database -func GetUser(ctx context.Context, client *firestore.Client, userID string) (*User, error) { - doc, err := client.Collection(users.String()).Doc(userID).Get(ctx) +func GetUser(ctx *context.Context, userID string) (*User, error) { + doc, err := client.Collection(users.String()).Doc(userID).Get(*ctx) if err != nil { return nil, err } @@ -32,13 +32,13 @@ func GetUser(ctx context.Context, client *firestore.Client, userID string) (*Use } // Save a user to the database -func SaveUser(ctx context.Context, client *firestore.Client, user User) error { - _, err := client.Collection(users.String()).Doc(user.ID).Set(ctx, user) +func SaveUser(ctx *context.Context, user User) error { + _, err := client.Collection(users.String()).Doc(user.ID).Set(*ctx, user) return err } // Iterate over all users -func ForEachUser(ctx context.Context, client *firestore.Client, callback func([]User) error) error { +func ForEachUser(ctx *context.Context, callback func([]User) error) error { var lastDoc *firestore.DocumentSnapshot for { query := client.Collection(users.String()).OrderBy(firestore.DocumentID, firestore.Asc).Limit(100) @@ -46,7 +46,7 @@ func ForEachUser(ctx context.Context, client *firestore.Client, callback func([] query = query.StartAfter(lastDoc) } - docs, err := query.Documents(ctx).GetAll() + docs, err := query.Documents(*ctx).GetAll() if err != nil { return err } diff --git a/internal/scheduler/database.go b/internal/scheduler/database.go deleted file mode 100644 index ac2104f..0000000 --- a/internal/scheduler/database.go +++ /dev/null @@ -1,18 +0,0 @@ -package scheduler - -import ( - "context" - "log" - - firestoreDB "cloud.google.com/go/firestore" - "github.com/capymind/internal/firestore" -) - -// Create a Firestore client -func createDBClient(ctx context.Context) *firestoreDB.Client { - var client, err = firestore.NewClient(ctx) - if err != nil { - log.Printf("[Scheduler] Error creating firestore client, %s", err.Error()) - } - return client -} diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go index 9e95cc2..6f91856 100644 --- a/internal/scheduler/scheduler.go +++ b/internal/scheduler/scheduler.go @@ -45,21 +45,17 @@ func Schedule(w http.ResponseWriter, r *http.Request) { } ctx := context.Background() - - // Firestore - dbClient := createDBClient(ctx) - defer dbClient.Close() + firestore.CreateClient(&ctx) // Cloud Tasks - tasksClient := createTasksClient(ctx) - defer tasksClient.Close() + CreateTasks(&ctx) var isCloud = false if os.Getenv("CLOUD") == "true" { isCloud = true } - firestore.ForEachUser(ctx, dbClient, func(users []firestore.User) error { + firestore.ForEachUser(&ctx, func(users []firestore.User) error { for _, user := range users { log.Printf("[Scheduler] Schedule a message for user: %s", user.ID) if user.Locale == nil || user.SecondsFromUTC == nil { @@ -70,7 +66,7 @@ func Schedule(w http.ResponseWriter, r *http.Request) { var localizedMessage string if messageType == WeeklyAnalysis { - notes, err := firestore.GetNotes(ctx, dbClient, user.ID) + notes, err := firestore.GetNotes(&ctx, user.ID) if err != nil { log.Printf("[Scheduler] Error getting notes from firestore, %s", err.Error()) continue @@ -83,7 +79,8 @@ func Schedule(w http.ResponseWriter, r *http.Request) { strings = append(strings, note.Text) } } - localizedMessage = *analysis.Request(strings, userLocale) + header := "weekly_analysis" + localizedMessage = *analysis.Request(strings, userLocale, &ctx, &header) } else { continue } @@ -107,10 +104,15 @@ func Schedule(w http.ResponseWriter, r *http.Request) { Locale: userLocale, } - scheduleTask(ctx, tasksClient, scheduledMessage, scheduledTime) + scheduleTask(&ctx, scheduledMessage, scheduledTime) } return nil }) + + // Close Firestore client + CloseTasks() + // Close Tasks client + firestore.CloseClient() } // Send a message to a user diff --git a/internal/scheduler/tasks.go b/internal/scheduler/tasks.go index 423c837..8606b3e 100644 --- a/internal/scheduler/tasks.go +++ b/internal/scheduler/tasks.go @@ -13,17 +13,24 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) +var client *cloudtasks.Client + // Create cloud tasks client -func createTasksClient(ctx context.Context) *cloudtasks.Client { - var client, err = cloudtasks.NewClient(ctx) +func CreateTasks(ctx *context.Context) { + var newClient, err = cloudtasks.NewClient(*ctx) if err != nil { log.Printf("[Scheduler] Error creating cloud tasks client, %s", err.Error()) } - return client + client = newClient +} + +// Close cloud tasks client +func CloseTasks() { + client.Close() } // Schedule a cloud task -func scheduleTask(ctx context.Context, client *cloudtasks.Client, scheduledMessage ScheduledMessage, timeOffset time.Time) { +func scheduleTask(ctx *context.Context, scheduledMessage ScheduledMessage, timeOffset time.Time) { projectID := os.Getenv("CAPY_PROJECT_ID") locationID := os.Getenv("CAPY_SERVER_REGION") queueID := "messages" @@ -55,7 +62,7 @@ func scheduleTask(ctx context.Context, client *cloudtasks.Client, scheduledMessa } req.Task.GetHttpRequest().Body = payload - createdTask, err := client.CreateTask(ctx, req) + createdTask, err := client.CreateTask(*ctx, req) if err != nil { log.Printf("[Scheduler] Error scheduling a task, %s", err.Error()) return diff --git a/internal/translator/translations.go b/internal/translator/translations.go index cfaa580..d6a2c09 100644 --- a/internal/translator/translations.go +++ b/internal/translator/translations.go @@ -19,9 +19,8 @@ const translationsJSON = `{ "make_record_to_journal_short": "Make a record 💭", "no_analysis": "Not enough entries have been made to generate an analysis. Begin by sharing your thoughts and feelings with CapyMind.", "analysis_waiting": "Your analysis is being generated. Please hold on for a moment 😴", - "ai_analysis_prompt": "You’re a professional therapist at CapyMind. You have received the following entries. (A brief summary, with entries sorted as follows: the most recent ones at the top of the list.) What is your professional opinion? Entries: ", "how_to_use": "Getting started 🙋‍♂️", - "weekly_analysis": "Analysis for the past week 🧑‍⚕️\n\n" + "weekly_analysis": "Weekly analysis 🧑‍⚕️\n\n" }, "uk": { "welcome": "Ласкаво просимо до CapyMind 👋 Ваш особистий журнал для записів про психічне здоров'я тут, щоб допомогти вам на вашому шляху. Рефлексуйте над своїми думками та емоціями, використовуйте нагадування, щоб залишатися на шляху, та досліджуйте інсайти терапії, щоб поглибити свою самосвідомість.", @@ -36,13 +35,23 @@ const translationsJSON = `{ "timezone_select": "Виберіть свій часовий пояс 👇", "timezone_set": "Ваш часовий пояс успішно оновлено 🤘 Ви почнете отримувати нагадування про ведення записів ⏰", "how_are_you_morning": "Доброго ранку! Як ви себе почуваєте сьогодні? Чи були у вас сни протягом ночі або думки на ранок, якими ви хочете поділитися?", - "how_are_you_evening": "Доброго вечора! Як пройшов ваш день? Чи є відгуки або думки, якими ви хочете поділитися перед відпочинком?", + "how_are_you_evening": "Доброго вечора! Як минув ваш день? Можливо, у вас є думки або враження, якими ви хотіли б поділитися перед сном?", "make_record_to_journal": "Зробити запис у свій журнал 💭", "make_record_to_journal_short": "Зробити запис 💭", "no_analysis": "Недостатньо записів для генерації аналізу. Почніть ділитись своїми думками та почуттями з CapyMind.", "analysis_waiting": "Ваш аналіз генерується. Будь ласка, зачекайте 😴", - "ai_analysis_prompt": "Ви професійний терапевт в Capymind. Ви отримуєте наступні записи. (Короткий зміст, записи відсортовані наступним чином: останні записи спочатку списку) Яка ваша професійна думка? Записи: ", "how_to_use": "Допомога 🙋‍♂️", "weekly_analysis": "Аналіз за останній тиждень 🧑‍⚕️\n\n" } }` + +const promptsJSON = `{ + "en": { + "ai_analysis_system_message": "You are a skilled therapist at CapyMind, specializing in reviewing and providing feedback on user journals. Your responses should be structured into three distinct parts: 1. Praise & Encouragement: Begin by acknowledging the user’s efforts, offering positive reinforcement for progress made. If progress is minimal, provide motivational support to encourage continued effort (2-3 sentences) 2. Analysis: Analyze the user’s recent journal entries, identifying key patterns or themes in their thoughts, emotions, or behaviors (up to 10 sentences) 3. Recommendations: Finish by offering 3-4 sentences of practical suggestions or steps the user can take to continue their personal growth. (Don't use 1, 2, 3 in the actual response and the names of the parts)", + "ai_analysis_user_message": "Below is a list of my recent journal entries. Please provide feedback: " + }, + "uk": { + "ai_analysis_system_message": "Ви є кваліфікованим терапевтом в CapyMind, який спеціалізується на перегляді та наданні відгуку аналізуючи записи користувачів. Ваші відповіді повинні бути структуровані на три відмінні частини: 1. Похвала та підтримка: Почніть з визнання зусиль користувача, запропонуйте позитивне підкріплення за досягнуті успіхи. Якщо прогрес мінімальний, надайте мотиваційну підтримку для підтримки подальших зусиль (2-3 речення) 2. Аналіз: Проаналізуйте нещодавні записи користувача, визначивши ключові шаблони або теми у їхніх думках, емоціях або поведінці (до 10 речень) 3. Рекомендації: Завершіть, запропонувавши 3-4 речення практичних порад або кроків, які користувач може зробити для особистого росту. (Не використовуйте 1, 2, 3 у фактичній відповіді та назви частин)", + "ai_analysis_user_message": "Нижче наведено список моїх нещодавніх записів у журналі. Будь ласка, надайте відгук. Записи: " + } +}` diff --git a/internal/translator/translator.go b/internal/translator/translator.go index 9e76398..01c8135 100644 --- a/internal/translator/translator.go +++ b/internal/translator/translator.go @@ -6,26 +6,38 @@ import ( ) var translations map[Locale]map[string]string +var prompts map[Locale]map[string]string func init() { if err := json.Unmarshal([]byte(translationsJSON), &translations); err != nil { log.Fatalf("Failed to parse JSON: %v", err) } + if err := json.Unmarshal([]byte(promptsJSON), &prompts); err != nil { + log.Fatalf("Failed to parse JSON: %v", err) + } } -func Translate(locale Locale, key string) string { - // Checks if the locale is in the translations map - if _, ok := translations[locale]; !ok { +func localize(data *map[Locale]map[string]string, locale Locale, key string) string { + // Checks if the locale is in the map + if _, ok := (*data)[locale]; !ok { log.Printf("Locale %s not found in translations", locale) return key } - // Checks if the key is in the translations map - if _, ok := translations[locale][key]; !ok { + // Checks if the key is in the map + if _, ok := (*data)[locale][key]; !ok { log.Printf("Key %s not found in translations", key) return key } - // Returns the translation - return translations[locale][key] + // Returns the localized string + return (*data)[locale][key] +} + +func Translate(locale Locale, key string) string { + return localize(&translations, locale, key) +} + +func Prompt(locale Locale, key string) string { + return localize(&prompts, locale, key) } diff --git a/internal/translator/translator_test.go b/internal/translator/translator_test.go index fc9db54..45ec384 100644 --- a/internal/translator/translator_test.go +++ b/internal/translator/translator_test.go @@ -6,7 +6,7 @@ func TestTranslator(t *testing.T) { locale1 := Locale("en") locale2 := Locale("uk") - want := "Analysis for the past week 🧑‍⚕️\n\n" + want := "Weekly analysis 🧑‍⚕️\n\n" if got := Translate(locale1, "weekly_analysis"); got != want { t.Errorf("Translate() = %v, want %v", got, want) @@ -22,6 +22,12 @@ func TestTranslator(t *testing.T) { if got := Translate(locale1, "no_existing_id"); got != want { t.Errorf("Translate() = %v, want %v", got, want) } + + // Test prompt + want = "Below is a list of my recent journal entries. Please provide feedback: " + if got := Prompt(locale1, "ai_analysis_user_message"); got != want { + t.Errorf("Prompt() = %v, want %v", got, want) + } } func TestTranslatorJSON(t *testing.T) { @@ -32,3 +38,12 @@ func TestTranslatorJSON(t *testing.T) { t.Errorf("Number of texts for en and uk locales is different") } } + +func TestPromptJSON(t *testing.T) { + en_prompts := prompts["en"] + uk_prompts := prompts["uk"] + + if len(en_prompts) != len(uk_prompts) { + t.Errorf("Number of prompts for en and uk locales is different") + } +}