diff --git a/README.md b/README.md index f6dc10d..c080782 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,60 @@ -# rasa_example_project -Example for setting up a conversational agent based on Rasa Open Source on a Google Compute Engine instance. The conversational agent in this example interacts with people in 5 conversational sessions. +# Dyadic Physical Activity Planning with a Virtual Coach: Using Reinforcement Learning to Select Persuasive Strategies -Based on this Github repository (https://github.com/AmirStudy/Rasa_Deployment) as well as the work by Tom Jacobs (https://github.com/TomJ-EU/rasa/tree/dev). +This github repository contains the code for the virtual coach Jamie, that is created for the thesis project titled "Dyadic Physical Activity Planning with a Virtual Coach: Using Reinforcement Learning to Select Persuasive Strategies." Jamie was used to gather data dring an observational study. Please refer to the [OSF pre-registration](https://doi.org/10.17605/OSF.IO/8ADP9) for more details on the observational study. For the code used to create a reinforcement learning model based on the data gathered, and for the code used for the analysis of this model, please refer to the [published data and code](https://doi.org/10.4121/2796f502-0610-4a7d-a8ee-ebc36639e0b1). +## Dialogue flow -## Components +In the observational study, people talked to the virtual code in a single session. During this session, the virtual coach first introduced itself and asked the person how they are feeling. Then, the person could pick a goal for walking, after which they were asked to indicate when they are free and how their energy levels change throughout the day. Using this information, an initial plan for taking walks was created. To gather the data needed for the reinforcement learning model, the virtual coach then asked three questions about the person's situation, composed on confidence in following the plan, perceived usefulness of planning, and attitude towards planning. Next, the virtual coach would use a random persuasive strategy and ask these questions again. This repeated until at least two persuasive strategies were used and until the person's confidence, perceived usefulness and attitude were high enough to assume that they would commit to the plan. Before ending the conversation, the virtual coach asked four more questions to gather the reward signal, composed on the person's satisfaction with the dialogue, their commitment ot the firts two weeks and to the entire plan, and their confidence in reaching the goal. -This virtual coach consists of a backend based on Rasa Open Source (backend), a custom action server (actions), a frontend (frontend), a database (db), an SQLTrackerStore, and Nginx. + + + +## System architecture + +### Frontend + +The frontend is a html-page. Accessing the page via localhost requires to provide a user id and session number in the URL. For example, `localhost/?userid=1` opens the conversation for the user with id 1. + +Files: +- static/css/style.css contains the stylesheet for the html-page. +- static/js/script.js contains the functions for the interaction between the user and the chatbot and for the communication between front- and backend. +- index.html contains the code for the frontend html-page. +- server.js contains the code to start the server and initialize the correct session. + +### Backend + +The backend is a combination of files that split the logic of what the chatbot should say, what internal actions should be taken and which variables to keep during the session. + +Files: +- data/rules.yml contains the rules of the chatbot, stating what to do when something is triggered by the user or chatbot itself. +- models contains the trained models for the chatbot to use with all the rules, actions and other information. +- domain.yml contains the actual phrases of the chatbot and the variables that need to be tracked during the conversation. + +### Actions + +The actions is a file containing all the logic in relation to database access and calculations needed during the conversation which can be used by the backend files. + +Files: +- actions.py containing all the logic in relation to database access and calculations needed during the conversation. + +### Database (db) + +The database is a Mysql database storing all the relevant information from the conversation. +Meanwhile, a Postgres database stores all the session details, for example, every utterance of the chatbot and user. + +## Running the virtual coach locally + +To run the chatbot locally: +1. Install Docker (in case you have not), which you can do by following the instructions [here](https://docs.docker.com/get-docker/). +2. Install Docker-compose by running: + - `sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose` + - `sudo chmod +x /usr/local/bin/docker-compose` +3. Create a fork and clone this project. +4. Navigate to the root folder of this project (where this README is located). +5. Run `./run.sh` to build the rasa model, build docker and bootup the server for the chatbot. +6. Open `localhost/?userid=` and replace \ and \ with the user id. + +Note that the virtual coach checks if there is already data for a specific user in the database and will prevent the same user from starting the conversation multiple times. The first time data is saved to the database is after the virtual coach asks the three repeating questions, so if a cetrain user id got to that point, it is saved in the database and when trying to start the conversation again, the virtual coach will not let you start the conversation. ## Setup on Google Compute Engine @@ -59,73 +107,15 @@ To run this project on a Google Compute Engine, I followed these steps: - And if you are not using Nginx, you also need to open port 3000 on your Google Compute Engine instance for tcp. - Open the chat here: - + - The button can be very small on your phone. - The chat should look something like this: - + - - Right now I have set the code in frontend/static/css/style.css such that the chat is always opened in fullscreen. See this code: - - ```css - .widget { - display: none; - width: 98%; - right: 1%; - left: 1%; - height: 98%; - bottom: 2%; - position: fixed; - background: #f7f7f7; - border-radius: 10px 10px 10px 10px; - box-shadow: 0px 2px 10px 1px #b5b5b5; - } - ``` - - The code by Tom Jacobs (https://github.com/TomJ-EU/rasa/tree/dev) instead first opens the chat as a smaller window and adds a "fullscreen"-option to the drop-down used in [the code by Jitesh Gaikwad](https://github.com/AmirStudy/Rasa_Deployment). For example, like this in script.js: - ```js - //fullscreen function to toggle fullscreen. - $("#fullscreen").click(function () { - if ($('.widget').width() == 350) { - $('.widget').css("width" , "98%"); - $('.widget').css("height" , "100%"); - } else { - $('.widget').css("width" , "350px"); - $('.widget').css("height" , "500px"); - } - }); - ``` - - But then you also need to make sure to add the drop-down to the file index.html: - - ```html -
- - - Virtual Coach Mel - - - more_vert - - -
- ``` - - ```html - - - ``` - - - And further adapt script.js by adding code to `(document).ready(function ()`: - - ```js - //drop down menu - $('.dropdown-trigger').dropdown(); - ``` - This project uses an SQLTrackerStore (https://rasa.com/docs/rasa/tracker-stores/) to store the conversation history in a database: - A nice way to see the contents of this database is using the program DBeaver. - First also open port 5432 on your Google Compute Engine instance for tcp. There is no need to restart the instance after opening the port. @@ -174,271 +164,6 @@ Some errors I got during the setup: - When running the project locally on Windows: - I got an error for the SQLTrackerStore when running `docker-compose up –-build`. Just removing the information on `volumes` in docker-compose.yml helped. This removes the persistence though. - Since adding nginx, nginx does not work out of the box. To just quickly get the project to work locally, I ignored the nginx part. So I accessed the frontend via "localhost:3000/?..." and changed the url in the file script.js to `url: "http://localhost:5005/webhooks/rest/webhook",`. - - -## Frontend Styling - -Check the file frontend/static/css/style.css to adapt the styling of the frontend: - - .chats defines the chat area within the window in fullscreen mode. I tuned the height and width of this. - - .chat_header_title defines the chat header title. I set the color to #f7f7f7 so that the title is not visible in fullscreen mode. Change the margin-left to align the title to the center. Right now I have fully removed the title though. If you want to add the title again, your file frontend/index.html should contain `chat_header_title`: - - ```html - -
-
- - - Your Bot Name - -
- - -
-
-
- - -
- -
-
- -
- ``` - - - If you want to change the way that buttons are displayed, adapt `.menu` and `.menuChips` in the file style.css. - - For example, you may want to display the buttons like this: - - - - - This can be done with this code: - - ```css - .menu { - padding: 5px; - display: flex; - flex-wrap: wrap; - } - - .menuChips { - display: inline-block; - background: #2c53af; - color: #fff; - padding: 5px; - margin-bottom: 5px; - cursor: pointer; - border-radius: 15px; - font-size: 14px; - } - ``` - - - Important is that `display: flex` and `flex-wrap: wrap` in `.menu`. - - To further remove the background of the buttons and add a shadow to the individual buttons instead, set `box-shadow: 2px 5px 5px 1px #dbdade` for `.menuChips` and use this code for `.suggestions` in the file style.css: - - ```css - .suggestions { - padding: 5px; - width: 80%; - border-radius: 10px; - background: #f7f7f7; - } - ``` - - - Then buttons are displayed like this: - - - - - See [this post](https://stackoverflow.com/questions/73533611/how-to-put-two-chips-divs-next-to-each-other) for some other ideas for displaying buttons next to each other. - - Note that by default, buttons are displayed like this: - - - - - The corresponding code in the file style.css looks like this: - - ```css - .menu { - padding: 5px; - } - - .menuChips { - display: block; - background: #2c53af; - color: #fff; - text-align: center; - padding: 5px; - margin-bottom: 5px; - cursor: pointer; - border-radius: 15px; - font-size: 14px; - word-wrap: break-word; - } - ``` - -The files in frontend/static/img are used to display the chatbot and the user inside the chat, as well as to display the chatbot when the chat is still closed at the start. - -You can use "\n" in your utterances in domain.yml to display a single utterance as two (or more) separate messages. The resulting messages are not treated as separate messages when it comes to displaying the typing symbol though. - - -## HTTPS - -You might want to allow also for https traffic: - - I recommend looking at [this tutorial](https://datahive.ai/deploying-rasa-chatbot-on-google-cloud-with-docker/). Compared to allowing only http-traffic, you have to make changes in nginx.conf and docker-compose.yml and create an SSL certificate. - - For example, this is what the entry for nginx in docker-compose.yml may look like when allowing https traffic: - - ```yml - nginx: - container_name: "nginx" - image: nginx - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf - - ./certs:/etc/certs - ports: - - 80:80 - - 443:443 - depends_on: - - rasa - - action-server - - chatbotui - ``` - - - The folder "certs" on the Google compute engine instance stores the SSL certificate files in this example. - - - See [this post](https://adamtheautomator.com/https-nodejs/) for how to create a self-signed SSL certificate. - - If you use a self-signed SSL certificate and access your frontend via https, you may see a warning like this in your browser (here Google Chrome): - - - - - This might scare participants off. So I would recommend to either stick to http or to go all the way and get a proper certificate. - - - See [this page](https://cloud.google.com/load-balancing/docs/ssl-certificates/self-managed-certs) for more information on certificates on Google cloud. - - Info on registering a domain: https://cloud.google.com/dns/docs/tutorials/create-domain-tutorial#register-domain. - - Registering a domain for a year is quite cheap (you can get one for about 8 euros). - - Cloud DNS pricing info: https://cloud.google.com/dns/pricing. You need this if you get a domain and want to use it. - - -## Getting the user name -- It is not a good idea to just get and use the user name as in this example project. This is because many people reply with things such as "Hi, my name is Mary" when being asked about their name. -- So it might be a good idea to not ask for and use the name at all. -- Some steps to try to improve getting the name: - - Use an entity and an intent for getting the entity in domain.yml: - - ```yml - intents: - - user_name_intent - - entities: - - user_name_entity: - influence_conversation: false - - slots: - user_name_slot: - type: text - initial_value: '' - influence_conversation: false - mappings: - - type: from_entity - entity: user_name_entity - conditions: - - active_loop: user_name_form - ``` - - - Create some training data for the intent in the file nlu.yml (remember to create a lot of training data): - - ```yml - nlu: - - - intent: user_name_intent - examples: | - - "Hi Mel, I'm [PERSON](user_name_entity)" - - "My name is [PERSON](user_name_entity)" - - "I'm [PERSON](user_name_entity)" - - "[PERSON](user_name_entity)" - ``` - - - Use spacy in config.yml. See [here](https://spacy.io/models/en) for different English language models. - - ```yml - language: en - pipeline: - - name: SpacyNLP - model: en_core_web_lg - case_sensitive: false - - name: SpacyTokenizer - - name: SpacyFeaturizer - pooling: mean - - name: SpacyEntityExtractor - dimensions: - - PERSON - - name: RegexFeaturizer - - name: LexicalSyntacticFeaturizer - - name: CountVectorsFeaturizer - ``` - - - Now that you use spacy, you also need to adapt the Dockerfile for your backend: - - ``` - USER root - - COPY requirements.txt . - RUN pip install -r requirements.txt - - # Spacy language model - RUN python -m spacy download en_core_web_lg - - USER 1001 - ``` - - - And backend/requirements.txt needs to list `spacy` as a requirement. - - When rasa does not succeed in extracting a slot in the `user_name_form` (e.g., when you try a not-so-typical English name such as "Priyanka"), then an ActionExecutionRejected event is thrown. - - A work-around is to then just store the last user utterance as `user_name_slot`. - - To do this, you might create a rule like this one: - - ```yml - - rule: name session 1 failed fallback - condition: - - active_loop: user_name_form - steps: - - intent: nlu_fallback - - action: action_get_name_from_last_utterance - - action: utter_confirm_name - - action: action_deactivate_loop - - active_loop: null - - action: utter_ask_for_mood_session1 - ``` - - - The action `action_get_name_from_last_utterance` just gets the text from the last user utterance via `last_user_utterance = tracker.latest_message['text']` and stores this in the slot `user_name_slot`. - - Since you are using spacy, you also need to have spacy installed where you train your rasa model. To do this in an anaconda environment, use `conda install -c conda-forge spacy`. - - And then you also need to download the language model you use. - - I personally got package version conflicts with rasa 3.2.8, so I used rasa 3.5.3 for the training. - - This also means that I updated the Dockerfile for the custom actions to use `FROM rasa/rasa-sdk:3.3.0` and the Dockerfile for the backend to use `FROM rasa/rasa:3.5.3-full`. - - Now this setup MIGHT allow you to correctly handle responses such as "My name is John" or "Priyanka": - - It is quite difficult to get this setup to work well in all cases: - - For example, the DietClassifier may also extract (wrong) entities, in which case you may need to look through all entities in `tracker.latest_message['entities']` in a custom action and choose the entity for which `entity["extractor"] == "SpacyEntityExtractor"`. - - Sometimes, rasa recognizes "user_name_intent" for "Priyanka" but cannot extract an entity. - - If the user uses the chatbot name in their message, sometimes the chatbot name is extracted as the user name. - - Unless you are confident that the user has a common English (or whichever language model you use) name and/or types only their name, I would suggest to not use the user name. In my pilot study, 3 out of 8 people typed more than their name. - - You could of course also play back to the user what you got as their name and ask them for confirmation, but this might make the virtual coach look rather stupid if what they play back as the user name is "My name is Priyanka". - - -## Other Notes -- The frontend is not fully cleaned up yet (i.e., still contains quite some components that are not used by this project). -- The repository by Jitesh Gaikwad (https://github.com/AmirStudy/Rasa_Deployment) also contains code for displaying charts, drop-downs, and images in frontend/static/js/script.js (see the function `setBotResponse` for displaying responses from the rasa bot). I have removed this code in this example project, but if you need to send such kinds of messages, take a look. -- `"--debug"` in backend/Dockerfile prints a lot of debugging statements (e.g., for the action prediction). This is handy while you are still developing your agent, but can be removed. -- The Developer tools in Google Chrome show the logs from script.js (i.e., the result of `console.log()`)if you access the frontend via Google Chrome. -- Think carefully about how you deal with timed out sessions. You may want to customize the `action_session_start`: https://rasa.com/docs/rasa/default-actions#customization. -- If you have made changes and they do not reflect on your Google Compute Engine instance, check if you have run `docker-compose down --volumes` and `docker-compose up --build`. -- If you do not see the result of retraining your rasa model, it can sometimes help to delete all models and retrain from scratch. -- You might want to prevent people from typing while the chatbot is still sending more messages. You can adapt the file script.js to allow for this using statements such as `$('.usrInput').attr("disabled",true);` and `$(".usrInput").prop('placeholder', "Wait for Mel's response.");` -- Before running the chatbot on a Google Compute Engine instance for your experiment, make sure to get a paid account. Once the trial period ends or you have used up your free credit your instance will stop. And a billing account will also help to prevent Google from stopping your project when it thinks that you are mining crypto currencies (e.g., see [here](https://groups.google.com/g/gce-discussion/c/5prZHD3DEnQ)). -- When using the db, pay attention to closing connections. Also pay attention to the kind of cursor you use when you use fetchone(). It may be good to use a buffered cursor then (e.g., see [here](https://stackoverflow.com/questions/29772337/python-mysql-connector-unread-result-found-when-using-fetchone)). -- You might want to get more detailed logs for your mysql database. See [here](https://stackoverflow.com/questions/39708213/enable-logging-in-docker-mysql-container) for a useful discussion. - - You can add `- ./mysql_log:/var/log/mysql` to your mysql volumes in docker-compose.yml. - - Create a file called mysql.log in /var/log/mysql in your mysql container after running `docker exec -it [mysql_container_id] /bin/bash` (e.g., via `cat > mysql.log`). - - Give sufficient permissions to this newly created file (e.g., via `chmod a+crw mysql.log`). - - Run `SET global general_log = 1;`, `SET global general_log_file='/var/log/mysql/mysql.log';` and `SET global log_output = 'file';` (e.g., via the console in DBeaver under SQL Editor > Open SQL console). - - Now you can see the logs on your Google Compute Engine instance in mysql_log/mysql.log. -- Viewing google activity logs: https://cloud.google.com/compute/docs/logging/activity-logs. -- Listing sessions/active connections on mysql server: https://dataedo.com/kb/query/mysql/list-database-sessions (e.g., can execute a query in DBeaver). ## License diff --git a/Readme_images/chat button.png b/Readme_images/chat button.png new file mode 100644 index 0000000..6550926 Binary files /dev/null and b/Readme_images/chat button.png differ diff --git a/Readme_images/chat.png b/Readme_images/chat.png new file mode 100644 index 0000000..35cc28a Binary files /dev/null and b/Readme_images/chat.png differ diff --git a/Readme_images/dialogue.png b/Readme_images/dialogue.png new file mode 100644 index 0000000..3dbf82e Binary files /dev/null and b/Readme_images/dialogue.png differ diff --git a/actions/actions.py b/actions/actions.py index 3bac21c..60e058a 100644 --- a/actions/actions.py +++ b/actions/actions.py @@ -8,6 +8,7 @@ from datetime import datetime from definitions import (DATABASE_HOST, DATABASE_PASSWORD, DATABASE_PORT, DATABASE_USER) +from itertools import combinations from rasa_sdk import Action, FormValidationAction, Tracker from rasa_sdk.executor import CollectingDispatcher from rasa_sdk.events import FollowupAction, SlotSet, UserUttered, ActionExecuted @@ -219,6 +220,31 @@ def round_to_nearest_5(n): def round_to_nearest_half(n): return round(n * 2.0) / 2.0 +def has_days(combination): + + days = [] + + for [time, energy] in combination: + + day = time.split("_")[0] + if day not in days: + days.append(day) + + return len(days) + +def energy_levels(combination): + + energy_levels = {} + + for [time, energy] in combination: + + if energy not in energy_levels: + energy_levels[energy] = 1 + else: + energy_levels[energy] += 1 + + return energy_levels + class ActionCreateInitialPlan(Action): @@ -321,30 +347,6 @@ def run(self, dispatcher: CollectingDispatcher, "sunday_evening" : [sunday_evening, weekends_evening] } - available_timeslots = [[day, days[day][1]] for day in days if days[day][0] == True] - - number_of_timeslots = len(available_timeslots) - - very_high_energy_timeslots = [[available, energy] for [available, energy] in available_timeslots if energy == '4'] - - high_energy_timeslots = [[available, energy] for [available, energy] in available_timeslots if energy == '3'] - - medium_energy_timeslots = [[available, energy] for [available, energy] in available_timeslots if energy == '2'] - - low_energy_timeslots = [[available, energy] for [available, energy] in available_timeslots if energy == '1'] - - very_low_energy_timeslots = [[available, energy] for [available, energy] in available_timeslots if energy == '0'] - - number_of_very_high_energy_timeslots = len(very_high_energy_timeslots) - - number_of_high_energy_timeslots = len(high_energy_timeslots) - - number_of_medium_energy_timeslots = len(medium_energy_timeslots) - - number_of_low_energy_timeslots = len(low_energy_timeslots) - - number_of_very_low_energy_timeslots = len(very_low_energy_timeslots) - minutes_week_1 = 120 if goal == "10000": @@ -353,65 +355,93 @@ def run(self, dispatcher: CollectingDispatcher, weekly_increase = 22 elif goal == "12000": weekly_increase = 25 - - if number_of_timeslots == 4: - - selected = available_timeslots - - else: - - select_slots = 4 - - if number_of_very_high_energy_timeslots > select_slots: - - selected = random.sample(very_high_energy_timeslots, select_slots) - - else: - selected = very_high_energy_timeslots - - select_slots -= number_of_very_high_energy_timeslots - - if number_of_high_energy_timeslots > select_slots: - - selected += random.sample(high_energy_timeslots, select_slots) - - else: + available_timeslots = [[day, days[day][1]] for day in days if days[day][0] == True] - selected += high_energy_timeslots + possibilities = list(combinations(available_timeslots, 4)) - select_slots -= number_of_high_energy_timeslots - if number_of_medium_energy_timeslots > select_slots: + with_three_days = [] - selected += random.sample(medium_energy_timeslots, select_slots) + for possibility in possibilities: + if has_days(possibility) >=3: + with_three_days.append(possibility) - else: + energies = {} + + for possibility in with_three_days: - selected += medium_energy_timeslots + energy = energy_levels(possibility) + + energies[str(possibility)] = energy + + for i in range(4): + + backup = energies.copy() + + without_i = {} + + for k,v in energies.items(): + + if f'{i}' not in v: + without_i[k] = v - select_slots -= number_of_medium_energy_timeslots + if (len(without_i) == 0): + energies = backup.copy() + else: + energies = without_i.copy() - if number_of_low_energy_timeslots > select_slots: + + largest_possible_energy = 0 - selected += random.sample(low_energy_timeslots, select_slots) + for k,v in energies.items(): + + energy = 0 + + for i in range(5): + if f'{i}' in v: + energy += i * v[f'{i}'] + + + if energy > largest_possible_energy: + largest_possible_energy = energy + + best_possibilities = [] + + for k,v in energies.items(): + + energy = 0 + + for i in range(5): + if f'{i}' in v: + energy += i * v[f'{i}'] + + if energy == largest_possible_energy: + best_possibilities.append(k) + + + picked = random.choice(best_possibilities) - else: + picked = picked[1:-1] - selected += low_energy_timeslots + split = picked.split(", [") - select_slots -= number_of_low_energy_timeslots + selected_times = [] - if select_slots is not 0: - - selected += random.sample(very_low_energy_timeslots, select_slots) + for item in split: + item = item.split(", ")[0] + + if "[" in item: + item = item[1:] + + item = item[1:-1] + + + selected_times.append(item) - # dispatcher.utter_message(text=f"Available slots: {available_timeslots}, Selected slots: {selected}") - duration_per_timeslot_week_1 = math.ceil(minutes_week_1/4) - selected_times = [time_energy[0] for time_energy in selected] custom_order = { "monday_morning": 0, "monday_midday": 1, "monday_afternoon": 2, "monday_evening": 3, @@ -427,6 +457,8 @@ def run(self, dispatcher: CollectingDispatcher, first = selected_times[0] + duration_per_timeslot_week_1 = math.ceil(minutes_week_1/4) + message = f"""Plan 1: Week 1 - {round_to_nearest_5(duration_per_timeslot_week_1)} minutes at these time slots: {selected_times}. Week 2 - {round_to_nearest_5(math.ceil((minutes_week_1 + weekly_increase)/4))} minutes at these time slots: {selected_times}. Week 3 - Walking for {round_to_nearest_half((minutes_week_1 + 2* weekly_increase)/60.0)} hours, distributed across 4 time slots. Week 4 - Walking for {round_to_nearest_half((minutes_week_1 + 3* weekly_increase)/60.0)} hours, distributed across 4 time slots. Month 2 - Walking for up to {round_to_nearest_half((minutes_week_1 + 7* weekly_increase)/60.0)} hours per week, distributed across 5 time slots. Month 3 - Walking for up to {round_to_nearest_half((minutes_week_1 + 11* weekly_increase)/60.0)} hours per week, distributed across 6 time slots.""" dispatcher.utter_message(text=message) @@ -494,6 +526,52 @@ async def run(self, dispatcher: CollectingDispatcher, return [] +class ActionCheckDialogueDone(Action): + def name(self): + return "action_check_dialogue_done" + + async def run(self, dispatcher: CollectingDispatcher, + tracker: Tracker, + domain: Dict[Text, Any]) -> List[Dict[Text, Any]]: + + + # check how many actions have been done + # after 2 actions, we can end the dialogue if states are good + + changes_to_plan = int(tracker.get_slot("changes_to_plan")) + + explain_planning = tracker.get_slot("explain_planning") + + identify_barriers = tracker.get_slot("identify_barriers") + + deal_with_barriers = tracker.get_slot("deal_with_barriers") + + show_testimonials = tracker.get_slot("show_testimonials") + + num_actions = changes_to_plan + explain_planning + identify_barriers + deal_with_barriers + show_testimonials + + if num_actions >= 2: + + if identify_barriers and deal_with_barriers or not identify_barriers: + + c = int(tracker.get_slot("confidence")) + + pu = int(tracker.get_slot("perceived_usefulness")) + + a = int(tracker.get_slot("attitude")) + + if c >= 8 and pu >= 8 and a >= 8: + + end = True + + else: + end = False + + if end: + + return [ActionExecuted("action_listen"), UserUttered(text="/confirm_actions_done", parse_data={"intent": {"name": "confirm_actions_done", "confidence": 1.0}})] + + return[ActionExecuted("action_listen"), UserUttered(text="/confirm_continue_dialogue", parse_data={"intent": {"name": "confirm_continue_dialogue", "confidence": 1.0}})] class ActionSelectAction(Action): def name(self): @@ -535,11 +613,12 @@ async def run(self, dispatcher: CollectingDispatcher, possible_actions = [] - # this corresponds to having done 3 actions, none of which were changes to the plan - # if we do the 4th action that is not a change to the plan, then we have to do changes to plans in turns 5 and 6 - # that shouldn't happen, since we don't want to make changes to plans twice in a row - if number_actions == 3 and changes_to_plan == 0: + # this corresponds to having done 1 action, which was not changes to the plan + # we want to give people the chance to change the plan at least once before ending the dialogue + if number_actions == 1 and changes_to_plan == 0: possible_actions = ["changes_to_plan"] + elif number_actions >= 2 and identify_barriers and not deal_with_barriers: + possible_actions = ["deal_with_barriers"] else: # we want to make at most 2 changes to the initial plan and to not change the plan twice in a row if last_action != "changes_to_plan" and changes_to_plan<=1: @@ -566,24 +645,73 @@ async def run(self, dispatcher: CollectingDispatcher, ch = tracker.get_slot("changes_to_plan") - c = tracker.get_slot("confidence") + c = int(tracker.get_slot("confidence")) - pu = tracker.get_slot("perceived_usefulness") + pu = int(tracker.get_slot("perceived_usefulness")) - a = tracker.get_slot("attitude") + a = int(tracker.get_slot("attitude")) + + + + if c in [0,1,2,3]: + current_c = "low" + elif c in [4,5,6]: + current_c = "medium" + elif c in [7,8,9,10]: + current_c = "high" + + if pu in range(-10,0): + current_pu = "low" + elif pu in range(0,11): + current_pu = "high" + + if a in range(-10,0): + current_a = "low" + elif a in range(0,11): + current_a = "high" # build current state state = f"{ch}, {c}, {pu}, {a}, {explain_planning}, {identify_barriers}, {deal_with_barriers}, {show_testimonials}" - query = ("SELECT * FROM state_action_state WHERE state_before = %s") + current_state = f"{ch}, {current_c}, {current_pu}, {current_a}, {explain_planning}, {identify_barriers}, {deal_with_barriers}, {show_testimonials}" - cur.execute(query, [state]) + query = ("SELECT * FROM state_action_state") - # retrieve all database entries which have an action taken from this state + cur.execute(query) + + # retrieve all database entries result = cur.fetchall() - # select only the actions in the database results - actions = [f"{action}" for (userid,date,state,action,next_state) in result] + # select all entries with a similar state + similar = [] + + for (userid,date,state,action,next_state) in result: + split = state.split(", ") + + if int(split[1]) in [0,1,2,3]: + db_c = "low" + elif int(split[1]) in [4,5,6]: + db_c = "medium" + elif int(split[1]) in [7,8,9,10]: + db_c = "high" + + if int(split[2]) in [0,1,2,3,4]: + db_pu = "low" + elif int(split[2]) in [5,6,7,8,9,10]: + db_pu = "high" + + if int(split[2]) in [0,1,2,3,4]: + db_a = "low" + elif int(split[2]) in [5,6,7,8,9,10]: + db_a = "high" + + db_state = f"{ch}, {db_c}, {db_pu}, {db_a}, {explain_planning}, {identify_barriers}, {deal_with_barriers}, {show_testimonials}" + + if db_state == current_state: + similar.append((userid,date,state,action,next_state)) + + # select only the actions in the database results + actions = [f"{action}" for (userid,date,state,action,next_state) in similar] # count how many times each action was done count = collections.Counter(actions) @@ -642,7 +770,7 @@ async def run(self, dispatcher: CollectingDispatcher, return [] -class ActionSelectAction(Action): +class ActionSaveAction(Action): def name(self): return "action_save_action" @@ -720,9 +848,9 @@ async def run(self, dispatcher: CollectingDispatcher, return [] -def save_goal_plans_and_reward_to_db(cur, conn, prolific_id, time, goal, plan_1, plan_2, plan_3, reward): - query = "INSERT INTO users(prolific_id, time, goal, plan_1, plan_2, plan_3, reward) VALUES(%s, %s, %s, %s, %s, %s, %s)" - cur.execute(query, [prolific_id, time, goal, plan_1, plan_2, plan_3, reward]) +def save_goal_plans_and_reward_to_db(cur, conn, prolific_id, time, goal, plan_1, plan_2, plan_3, reward, testimonial_1, takeaway_1, testimonial_2, takeaway_2, identified_barrier, barrier_description, barrier_strategy_1, barrier_strategy_2, planning_relevance, planning_importance_explanation): + query = "INSERT INTO users(prolific_id, time, goal, plan_1, plan_2, plan_3, reward, testimonial_1, takeaway_1, testimonial_2, takeaway_2, identified_barrier, barrier_description, barrier_strategy_1, barrier_strategy_2, planning_relevance, planning_importance_explanation) VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" + cur.execute(query, [prolific_id, time, goal, plan_1, plan_2, plan_3, reward, testimonial_1, takeaway_1, testimonial_2, takeaway_2, identified_barrier, barrier_description, barrier_strategy_1, barrier_strategy_2, planning_relevance, planning_importance_explanation]) conn.commit() class ActionSaveGoalPlansAndReward(Action): @@ -745,6 +873,7 @@ async def run(self, dispatcher: CollectingDispatcher, port=DATABASE_PORT, database='db' ) + cur = conn.cursor(prepared=True) prolific_id = tracker.current_state()['sender_id'] @@ -765,9 +894,39 @@ async def run(self, dispatcher: CollectingDispatcher, confidence_goal = tracker.get_slot("confidence_goal") + testimonial_1_intro = tracker.get_slot("testimonial_1_intro") + + testimonial_1_body = tracker.get_slot("testimonial_1_body") + + testimonial_2_intro = tracker.get_slot("testimonial_2_intro") + + testimonial_2_body = tracker.get_slot("testimonial_2_body") + + testimonial_1 = f"{testimonial_1_intro} {testimonial_1_body}" + + testimonial_2 = f"{testimonial_2_intro} {testimonial_2_body}" + + takeaway_1 = tracker.get_slot("takeaway_1") + + takeaway_2 = tracker.get_slot("takeaway_2") + + identified_barrier = tracker.get_slot("identified_barrier") + + barrier_description = tracker.get_slot("barrier_description") + + barrier_strategy_1 = tracker.get_slot("barrier_strategy_1") + + barrier_strategy_2 = tracker.get_slot("barrier_strategy_2") + + planning_relevance = tracker.get_slot("planning_relevance") + + planning_importance_explanation = tracker.get_slot("planning_importance_explanation") + + + reward = f"Reward: satifaction = {satisfaction}, commitment_1 = {commitment_1}, commitment_f = {commitment_f}, confidence_goal = {confidence_goal}" - save_goal_plans_and_reward_to_db(cur, conn, prolific_id, formatted_date, goal, plan_1, plan_2, plan_3, reward) + save_goal_plans_and_reward_to_db(cur, conn, prolific_id, formatted_date, goal, plan_1, plan_2, plan_3, reward, testimonial_1, takeaway_1, testimonial_2, takeaway_2, identified_barrier, barrier_description, barrier_strategy_1, barrier_strategy_2, planning_relevance, planning_importance_explanation) except mysql.connector.Error as error: logging.info("Error in saving name to db: " + str(error)) diff --git a/backend/data/rules.yml b/backend/data/rules.yml index 6d03c6a..dd715ea 100644 --- a/backend/data/rules.yml +++ b/backend/data/rules.yml @@ -70,40 +70,22 @@ rules: - action: utter_prompt_usage_2 - action: utter_explain_usage -- rule: confirm usage explain purpose +- rule: intro steps: - intent: confirm_usage - action: utter_button - - action: utter_purpose_1 - - action: utter_purpose_2 - - action: utter_purpose_3 - - action: utter_continue_purpose_1 + - action: utter_intro_1 + - action: utter_intro_2 + - action: utter_intro_3 + - action: utter_intro_4 -- rule: continue purpose 1 - steps: - - intent: confirm_continue_purpose_1 - - action: utter_purpose_4 - - action: utter_purpose_5 - - action: utter_purpose_6 - - action: utter_confirm_purpose - -- rule: confirm purpose explain planning - steps: - - intent: confirm_purpose - - action: utter_explain_usefulness_planning_1 - - action: utter_explain_usefulness_planning_2 - - action: utter_explain_usefulness_planning_3 - - action: utter_explain_usefulness_planning_4 - - action: utter_explain_usefulness_planning_question - rule: confirm usefulness planning explain goal steps: - - intent: confirm_usefulness_planning_intro + - intent: confirm_intro - action: utter_explain_goal_1 - action: utter_explain_goal_2 - - action: utter_explain_goal_3 - - action: utter_explain_goal_4 - action: utter_explain_goal_question - rule: set goal @@ -111,37 +93,23 @@ rules: - intent: confirm_explain_goal - action: utter_set_goal_1 - action: utter_set_goal_2 - - action: utter_set_goal_3 - action: utter_set_goal_continue - rule: set goal continue steps: - intent: confirm_set_goal_continue + - action: utter_set_goal_3 - action: utter_set_goal_4 - - action: utter_set_goal_5 - action: utter_set_goal - rule: confirm goal steps: - intent: confirm_goal - action: utter_confirm_goal - -- rule: internal motivation - steps: - - intent: confirm_initial_plan - - action: utter_internal_motivation_1 - - action: utter_internal_motivation_2 - - action: utter_internal_motivation_question - -- rule: ask about routines - steps: - - intent: confirm_motivation - - action: utter_thank_for_motivation - action: utter_routines_1 - action: utter_routines_2 - - action: utter_routines_3 - - action: utter_routines_4 - - action: utter_timeslots + - action: utter_timeslots_1 + - action: utter_timeslots_2 - rule: ask about energy levels @@ -204,23 +172,24 @@ rules: - rule: confirm confidence ask perceived usefulness steps: - intent: confirm_confidence - # - action: utter_confidence_repeat - action: utter_thank_for_confidence - action: utter_state_perceived_usefulness - rule: confirm perceived usefulness ask attitude steps: - intent: confirm_perceived_usefulness - # - action: utter_perceived_usefulness_repeat - action: utter_thank_for_perceived_usefulness - action: utter_state_attitude - rule: confirm attitude do action ask confidence steps: - intent: confirm_attitude - # - action: utter_attitude_repeat\ - # - action: utter_thank_for_attitude - action: action_save_event_state + - action: action_check_dialogue_done + +- rule: confirm attitude do action ask confidence + steps: + - intent: confirm_continue_dialogue - action: action_select_action - rule: explain planning @@ -377,7 +346,6 @@ rules: - action: utter_changes_to_plan_4 - action: utter_changes_to_plan_5 - action: utter_changes_to_plan_6 - # - action: utter_changes_to_plan_7 - rule: receive modified plan @@ -411,8 +379,8 @@ rules: steps: - intent: confirm_confidence_goal - action: utter_thank - - action: utter_email_reminder - action: utter_prolific_link + - action: utter_post - action: utter_goodbye - action: action_save_goal_plans_and_reward - action: action_rearrange_db diff --git a/backend/domain.yml b/backend/domain.yml index 18fb757..7b9f64a 100644 --- a/backend/domain.yml +++ b/backend/domain.yml @@ -20,6 +20,10 @@ intents: # usage - confirm_usage +# intro + +- confirm_intro + # goal - confirm_explain_goal @@ -108,6 +112,8 @@ intents: ## actions +- confirm_continue_dialogue + - do_explain_planning - do_identify_barriers - do_deal_with_barriers @@ -282,6 +288,14 @@ entities: - plan_check_first_walk: influence_conversation: false + +- first_walk: + influence_conversation: false + + +- week_3: + influence_conversation: false + - goal: influence_conversation: false @@ -303,6 +317,9 @@ entities: - explain_planning: influence_conversation: false +- planning_importance_explanation: + influence_conversation: false + - identify_barriers: influence_conversation: false @@ -318,6 +335,12 @@ entities: - barrier_description: influence_conversation: false +- barrier_strategy_1: + influence_conversation: false + +- barrier_strategy_2: + influence_conversation: false + - planning_relevance: influence_conversation: false @@ -333,6 +356,12 @@ entities: - testimonial_2_body: influence_conversation: false +- takeaway_1: + influence_conversation: false + +- takeaway_2: + influence_conversation: false + - action: influence_conversation: false @@ -769,6 +798,16 @@ slots: type: categorical influence_conversation: false values: + - -10 + - -9 + - -8 + - -7 + - -6 + - -5 + - -4 + - -3 + - -2 + - -1 - 0 - 1 - 2 @@ -788,6 +827,16 @@ slots: type: categorical influence_conversation: false values: + - -10 + - -9 + - -8 + - -7 + - -6 + - -5 + - -4 + - -3 + - -2 + - -1 - 0 - 1 - 2 @@ -803,6 +852,21 @@ slots: - type: from_entity entity: attitude + first_walk: + type: text + influence_conversation: false + mappings: + - type: from_entity + entity: first_walk + + + week_3: + type: text + influence_conversation: false + mappings: + - type: from_entity + entity: week_3 + last_action: type: text influence_conversation: false @@ -848,6 +912,14 @@ slots: - type: from_entity entity: planning_relevance + planning_importance_explanation: + type: text + initial_value: '' + influence_conversation: false + mappings: + - type: from_entity + entity: planning_importance_explanation + identify_barriers: type: bool initial_value: false @@ -872,11 +944,28 @@ slots: barrier_description: type: text + initial_value: '' influence_conversation: false mappings: - type: from_entity entity: barrier_description + barrier_strategy_1: + type: text + initial_value: '' + influence_conversation: false + mappings: + - type: from_entity + entity: barrier_strategy_1 + + barrier_strategy_2: + type: text + initial_value: '' + influence_conversation: false + mappings: + - type: from_entity + entity: barrier_strategy_2 + deal_with_barriers: type: bool initial_value: false @@ -895,6 +984,7 @@ slots: testimonial_1_intro: type: text + initial_value: '' influence_conversation: false mappings: - type: from_entity @@ -902,6 +992,7 @@ slots: testimonial_1_body: type: text + initial_value: '' influence_conversation: false mappings: - type: from_entity @@ -909,6 +1000,7 @@ slots: testimonial_2_intro: type: text + initial_value: '' influence_conversation: false mappings: - type: from_entity @@ -916,11 +1008,28 @@ slots: testimonial_2_body: type: text + initial_value: '' influence_conversation: false mappings: - type: from_entity entity: testimonial_2_body + takeaway_1: + type: text + initial_value: '' + influence_conversation: false + mappings: + - type: from_entity + entity: takeaway_1 + + takeaway_2: + type: text + initial_value: '' + influence_conversation: false + mappings: + - type: from_entity + entity: takeaway_2 + actions_done: type: bool @@ -934,6 +1043,16 @@ slots: type: categorical influence_conversation: false values: + - -10 + - -9 + - -8 + - -7 + - -6 + - -5 + - -4 + - -3 + - -2 + - -1 - 0 - 1 - 2 @@ -954,6 +1073,16 @@ slots: type: categorical influence_conversation: false values: + - -10 + - -9 + - -8 + - -7 + - -6 + - -5 + - -4 + - -3 + - -2 + - -1 - 0 - 1 - 2 @@ -973,6 +1102,16 @@ slots: type: categorical influence_conversation: false values: + - -10 + - -9 + - -8 + - -7 + - -6 + - -5 + - -4 + - -3 + - -2 + - -1 - 0 - 1 - 2 @@ -1018,41 +1157,86 @@ responses: utter_greet_2: - text: "I'm happy to meet you." - utter_confirm_mood: - buttons: - payload: /confirm_mood_very_good - title: "I'm doing fantastic today!" + title: "Happy" + - payload: /confirm_mood_very_good + title: "Glad" + - payload: /confirm_mood_very_good + title: "Pleased" + - payload: /confirm_mood_very_good + title: "Delighted" + - payload: /confirm_mood_very_good + title: "Serene" + - payload: /confirm_mood_very_good + title: "Content" + - payload: /confirm_mood_good + title: "Satisfied" + - payload: /confirm_mood_good + title: "Relaxed" + - payload: /confirm_mood_good + title: "Calm" + - payload: /confirm_mood_good + title: "Excited" - payload: /confirm_mood_good - title: "I'm doing good." + title: "Astonished" + - payload: /confirm_mood_good + title: "Aroused" + - payload: /confirm_mood_neutral + title: "Sleepy" + - payload: /confirm_mood_neutral + title: "Neutral" + - payload: /confirm_mood_neutral + title: "Tired" - payload: /confirm_mood_neutral - title: "I'm alright." + title: "Tense" - payload: /confirm_mood_bad - title: "Not so good…" + title: "Alarmed" + - payload: /confirm_mood_bad + title: "Afraid" + - payload: /confirm_mood_bad + title: "Droopy" + - payload: /confirm_mood_bad + title: "Bored" + - payload: /confirm_mood_very_bad + title: "Angry" - payload: /confirm_mood_very_bad - title: "Quite bad…" - text: "How are you doing today?" + title: "Annoyed" + - payload: /confirm_mood_very_bad + title: "Frustrated" + - payload: /confirm_mood_very_bad + title: "Distressed" + - payload: /confirm_mood_very_bad + title: "Depressed" + - payload: /confirm_mood_very_bad + title: "Sad" + - payload: /confirm_mood_very_bad + title: "Gloomy" + - payload: /confirm_mood_very_bad + title: "Miserable" + text: "How are you doing today? Pick one of the options below to let me know how you feel." utter_mood_very_good_response: - - text: "That's awesome!" + - text: "That's awesome! I'm glad you're in a great mood." utter_mood_good_response: - - text: "That's good to hear!" + - text: "That's good to hear! I'm glad you're in a good mood." utter_mood_neutral_response: - - text: "Ah, so a pretty regular day, then. Okay." + - text: "Ah, so a pretty regular day, then. Okay!" utter_mood_bad_response: - - text: "I'm sorry to hear that. I hope our conversation might cheer you up a bit." + - text: "I'm sorry to hear that! I hope our conversation might cheer you up a bit." utter_mood_very_bad_response: - - text: "I'm sorry to hear that. Do talk to someone if you have problems you need help with. For now, I hope our conversation might cheer you up a little bit." + - text: "I'm sorry to hear that! Do talk to someone if you have problems you need help with. For now, I hope our conversation might cheer you up a little bit." utter_prompt_usage_1: - text: "Before we start, let me explain how you can communicate with me." utter_prompt_usage_2: - - text: "You can simply click on one of the buttons, like you already did. Sometimes, you will have to use the chat box bellow to type in your answer." + - text: "You can simply click on one of the buttons like you already did. Sometimes, you will have to use the chat box below to type in your answer." utter_explain_usage: - buttons: @@ -1061,34 +1245,37 @@ responses: text: "Other times, you will need to do something on the left side of the screen. I will tell you when that is the case. Let me know when you are ready to continue by clicking the button." utter_button: - - text: "Nice. Seems like you're getting familiar with using the buttons." + - text: "Nice! Seems like you're getting familiar with using the buttons." + + utter_intro_1: + - text: "Let me briefly reiterate what you previously read in the introduction part of this session." + + utter_intro_2: + - text: "My objective is to help people create plans for taking walks." + + utter_intro_3: + - text: "We'll set a goal for you to reach in 6 months from now, and then create a plan for doing so." + + utter_intro_4: + - buttons: + - payload: /confirm_intro + title: "Let's continue." + text: "Let me know when you are ready to continue." utter_purpose_1: - - text: "Let me tell you a bit about myself. My goal is to help people create plans for taking walks during the week." + - text: "Let me tell you a bit about myself. My goal is to help people create plans for taking walks." utter_purpose_2: - - text: "Walking is generally regarded as beneficial for both health and mood." - - utter_purpose_3: - - text: "Another advantage of taking regular walks is that they are easily accessible for everyone." + - text: "Walking is generally regarded as beneficial for both health and mood, and it's also easily accessible for everyone, since it doesn't take a lot of time like a workout might, nor does it require special equipment." utter_continue_purpose_1: - buttons: - payload: /confirm_continue_purpose_1 title: "Let's continue." - text: "This means that it doesn't take a lot of time like a workout might, nor does it require special equipment. Let me know when you are done reading so we can continue." - - utter_purpose_4: - - text: "Okay! Let me explain in a bit more detail the kinds of walks we will make a plan for." + text: "Let me know when you are done reading so we can continue." - utter_purpose_5: - - text: "Unlike leisurely walks, you'll have to walk at moderate intensity for our plan to be effective." - - utter_purpose_6: - - text: "Moderate intensity is characterised by breathing more heavily than usual, while still being able to hold a short conversation." - - utter_purpose_7: - - text: "In other words, you should walk at a pace that makes it somewhat challenging for you to keep going, which is about 100 steps per minute for the average person." + utter_purpose_3: + - text: "Okay! For our plan, you'll have to walk at moderate intensity, which means you should be breathing more heavily than usual, while still being able to hold a short conversation." utter_confirm_purpose: - buttons: @@ -1097,16 +1284,10 @@ responses: text: "Once you are done reading, I will explain how this all ties together with the help of the plan." utter_explain_usefulness_planning_1: - - text: "Nice. So, how does planning help you make progress towards your goal?" + - text: "Nice. So, how does planning help you make progress toward your goal?" utter_explain_usefulness_planning_2: - - text: "If you have a clear indication of when you should go for a walk, it will be much easier for you to actually do it." - - utter_explain_usefulness_planning_3: - - text: "You'll have a clear schedule set in place, which will make it more difficult to avoid taking a walk or to postpone it." - - utter_explain_usefulness_planning_4: - - text: "After all, there is only so much time in a week, so, by making a plan, you integrate walking into your schedule such that you actually have time to do it." + - text: "If you have a clear indication of when you should go for a walk, you can integrate walking into your schedule such that you actually have time to do it." utter_explain_usefulness_planning_question: - buttons: @@ -1115,30 +1296,21 @@ responses: text: "Let me know when you are done reading and you want to continue." utter_explain_goal_1: - - text: "Okay! In order to create a plan for walking, we should first set a goal for you." + - text: "Alright, let's keep going. To create a plan for walking, we should first set a goal for you." utter_explain_goal_2: - - text: "Having a clear goal to work towards is helpful in keeping you motivated." - - utter_explain_goal_3: - - text: "However, for the goal to be most effective, you should pick a goal is both challenging and achievable." - - utter_explain_goal_4: - - text: "You should feel like you have to put in the effort to reach your goal, while avoiding aiming for something unrealistic." + - text: "Having a clear goal that is both challenging and achievable helps keep you motivated." utter_explain_goal_question: - buttons: - payload: /confirm_explain_goal title: "Let's move on." - text: "Let me know when you are ready to continue." + text: "Let me know when you are ready to continue with setting a goal." utter_set_goal_1: - - text: "I think we are ready for you to set a goal for walking." + - text: "Great, let's carry on! A common way of measuring how much you walk is through counting the number of steps you take in a day." utter_set_goal_2: - - text: "A common way of measuring how much you walk is through counting the number of steps you take in a day." - - utter_set_goal_3: - text: "And a common goal is to aim for 10000 steps per day, as this is beneficial for your health, especially if you are currently inactive." utter_set_goal_continue: @@ -1147,10 +1319,10 @@ responses: title: "I am ready." text: "Once you are ready to continue, please click the button." - utter_set_goal_4: - - text: "Okay. I assume you take at least about 2000 steps per day through your regular activities, so one of your options is to increase that amount by 8000 steps to reach a total of 10000 steps per day." + utter_set_goal_3: + - text: "Okay, let's proceed! I assume you take at least about 2000 steps per day through your regular activities, so one of your options is to increase that amount by 8000 steps to reach a total of 10000 steps per day." - utter_set_goal_5: + utter_set_goal_4: - text: "However, you can also pick a higher goal if you want a challenge!" utter_set_goal: @@ -1161,54 +1333,22 @@ responses: title: "11000 steps (or 110 minutes) per day." - payload: /confirm_goal{"goal":"12000"} title: "12000 steps (or 120 minutes) per day." - text: "Please pick one of these options as your goal." + text: "Please pick one of these options as your goal. Remember that this goal represents how many steps you will be able to do in six months from know." utter_confirm_goal: - - buttons: - - payload: /confirm_initial_plan - title: "I am prepared." - text: "Great! Take walks regularly and you'll be able to achieve your goal of {goal} steps per day. Now, tell me when you are ready to continue." - - - utter_internal_motivation_1: - - text: "Okay! An important part of achieving your goal is being motivated." - - utter_internal_motivation_2: - - text: "So, I am curious: what motivates you to want to walk regularly?" - - utter_internal_motivation_question: - - buttons: - - payload: /confirm_motivation - title: "Family." - - payload: /confirm_motivation - title: "Health." - - payload: /confirm_motivation - title: "Relationships." - - payload: /confirm_motivation - title: "Personal growth." - - payload: /confirm_motivation - title: "Work." - - payload: /confirm_motivation - title: "Friends." - text: "Please pick one of the options below:" - - utter_thank_for_motivation: - - text: "I see, thank you for letting me know!" + - text: "Great! Take walks regularly and you'll be able to achieve your goal of {goal} steps per day." utter_routines_1: - - text: "Alright, let's move on to creating the plan. In order to do that, let's first see when you have free time." + - text: "Let's move on to creating the plan. To do that, let's first see when you have free time." utter_routines_2: - - text: "You don't have to provide me with your exact daily schedule, but let me know if you have any routines in place." + - text: "Ideally, we're looking for at least four different days during the week when you have at least 30 minutes to take a walk." - utter_routines_3: - - text: "For example, most people work a day job. But maybe you have things planned on some evenings as well." + utter_timeslots_1: + - text: "On the left side of the screen, you can see a table. You can click on a time slot in the table to specify that you are available at that time. By clicking that slot again, you indicate that you are not available." - utter_routines_4: - - text: "Ideally, we're looking for at least four different times during the week when you have at least 30 minutes to take a walk." - - utter_timeslots: - - text: "On the left side of the screen, you can see a table. You can click on a time slot in the table to specify that you are available at that time. Please select all the slots when you are avaialable." + utter_timeslots_2: + - text: "Please select all the slots when you are available. Once you are done, click the button that says 'I'm done selecting time slots when I am free.' You may need to scroll to see this button." utter_confirm_sunday_slots: @@ -1216,44 +1356,44 @@ responses: utter_thank_for_times: - - text: "Thank you for letting me know when you are available." + - text: "Thank you for letting me know when you are available!" utter_energy_levels: - text: "Now, I want to ask when you have more energy than usual. We could make use of this opportunity to schedule a walk if you are also free at the time." utter_energy_instruction: - - text: "Like before, I would like you to pay attention to the left side of the sceen. Please specify how much energy you have during the weekdays and during the weekends by clicking the corresponding box for each time of the day." + - text: "Like before, I would like you to pay attention to the left side of the screen. Please specify how much energy you have during the weekdays and during the weekends by clicking the corresponding box for each time of the day. Note again that you may need to scroll to see the button which confirms that you are done." utter_energy_levels_weekends_confirm: - text: "So those are your energy levels on weekends:\n Morning: {weekends_morning}, Midday: {weekends_midday}, Afternoon: {weekends_afternoon}, Evening: {weekends_evening}." utter_thank_for_energy_levels: - - text: "Thank you for letting me know when you have more energy than usual." + - text: "Thank you for letting me know about your energy levels!" utter_present_plan_1: - buttons: - payload: /confirm_ready_initial_plan title: "Show me the plan." - text: "Now, let's see initial plan! Don't worry if it's not entirely to you liking yet. You'll have opportunities to modify it later. Please let me know when you are ready to continue." + text: "Now, let's see the initial plan! It is based on the times when you are free and the energy levels you specified, but don't worry if it's not entirely to your liking yet. You'll have opportunities to modify it later. Please let me know when you are ready to continue." utter_present_plan_2: - buttons: - payload: /confirm_checked_plan{"changes_to_plan":"0"} title: "Let's discuss the plan." - text: "I'll leave the plan on the left side on the screen so you can take a look at it later. Note that you may need to scroll to see the entire plan, depending on your screen resolution. Let me know when you are prepared to discuss the plan in more detail. " + text: "I'll leave the plan on the left side of the screen so you can take a look at it later. Note that you may need to scroll to see the entire plan, depending on your screen resolution. Let me know when you are prepared to discuss the plan in more detail." utter_present_plan_3: - - text: "Okay, let's take a took at the plan. On the left side of your screen, you should see a big table with a view similar to that of a calendar. Time slots when you should take walks are marked with the duration you should walk for. You can also notice that the duration increases each week." + - text: "Okay, let's take a look at the plan! On the left side of your screen, you should see a big table with a view similar to that of a calendar. Time slots when you should take walks are marked with the duration you should walk for. You can also notice that the duration increases each week." utter_check_plan_first_walk: - text: "You can see the first two weeks planned in detail. To check that you understand what the plan signifies, let's do a quick pop quiz! When is the first time you have to take a walk? Please type your answer as day_time. For example, if your first walk is on Friday morning, please type 'friday_morning'." utter_check_plan_first_walk_correct: - - text: "That's right, your first walk is on {plan_check_first_walk}." + - text: "That's right, your first walk is on {plan_check_first_walk}!" utter_check_plan_first_walk_incorrect: - - text: "Unfortunately, you are not right. Please, take another look at the plan. You can see the first two weeks planned in detail. Look for the first day with a time slot selected, then look at what time in that day you should take a walk. Please type your answer as day_time." + - text: "Unfortunately, you are not right. Please, take another look at the plan. You can see the first two weeks planned in detail. Look for the first day with a time slot selected, then look at what time during that day you should take a walk. Please type your answer as day_time." utter_explain_weeks: - buttons: @@ -1262,7 +1402,7 @@ responses: text: "Moving on, you can also see the third and fourth weeks, but only in terms of how many hours you have to walk and how often. For the two months after that, you see only how much you will have to do in the last week of that month, to give you an idea of the effort that you are building up towards. Let me know when you are done reading." utter_check_plan_week_3: - - text: "Let's do an extra check to make sure everything is clear. How much time will you have to walk on your third week? Please type your answer as a number with one decimal point. For example, if your answer is 1 and a half hours, please type '1.5'" + - text: "Let's do an extra check to make sure everything is clear. How much time will you have to walk in your third week? Please type your answer as a number with one decimal point. For example, if your answer is 1 and a half hours, please type '1.5'" utter_check_plan_week_3_correct: - text: "Correct! In your third week, you will have to walk for {plan_check_week_3} hours." @@ -1303,14 +1443,14 @@ responses: title: "9" - payload: /confirm_confidence{"confidence":"10"} title: "10 (Very confident)" - text: "How confident are you that you can follow the plan?" + text: "How confident are you that you can follow the whole plan for 3 months?" utter_confidence_repeat: - text: "So your confidence is {confidence}." utter_thank_for_confidence: - - text: "I see. Thank you for telling me." - - text: "Okay. Good to know. Thank you." + - text: "I see. Thank you for telling me!" + - text: "Okay! Good to know. Thank you." - text: "Okay! Thanks for letting me know." - text: "Good to know. Thanks!" - text: "Got it. Thanks for letting me know!" @@ -1318,8 +1458,28 @@ responses: utter_state_perceived_usefulness: - buttons: + - payload: /confirm_perceived_usefulness{"perceived_usefulness":"-10"} + title: "-10 (I think it can hinder me very much)" + - payload: /confirm_perceived_usefulness{"perceived_usefulness":"-9"} + title: "-9" + - payload: /confirm_perceived_usefulness{"perceived_usefulness":"-8"} + title: "-8" + - payload: /confirm_perceived_usefulness{"perceived_usefulness":"-7"} + title: "-7" + - payload: /confirm_perceived_usefulness{"perceived_usefulness":"-6"} + title: "-6" + - payload: /confirm_perceived_usefulness{"perceived_usefulness":"-5"} + title: "-5" + - payload: /confirm_perceived_usefulness{"perceived_usefulness":"-4"} + title: "-4" + - payload: /confirm_perceived_usefulness{"perceived_usefulness":"-3"} + title: "-3" + - payload: /confirm_perceived_usefulness{"perceived_usefulness":"-2"} + title: "-2" + - payload: /confirm_perceived_usefulness{"perceived_usefulness":"-1"} + title: "-1" - payload: /confirm_perceived_usefulness{"perceived_usefulness":"0"} - title: "0 (I don't think it can help me)" + title: "0" - payload: /confirm_perceived_usefulness{"perceived_usefulness":"1"} title: "1" - payload: /confirm_perceived_usefulness{"perceived_usefulness":"2"} @@ -1339,15 +1499,15 @@ responses: - payload: /confirm_perceived_usefulness{"perceived_usefulness":"9"} title: "9" - payload: /confirm_perceived_usefulness{"perceived_usefulness":"10"} - title: "10 (I think it can help me)" - text: "To what extent do you think planning can help you take walks?" + title: "10 (I think it can help me very much)" + text: "To what extent do you think planning can help or hinder you in terms of taking walks?" utter_perceived_usefulness_repeat: - text: "So your perceived usefulness is {perceived_usefulness}." utter_thank_for_perceived_usefulness: - - text: "I see. Thank you for telling me." - - text: "Okay. Good to know. Thank you." + - text: "I see. Thank you for telling me!" + - text: "Okay! Good to know. Thank you." - text: "Okay! Thanks for letting me know." - text: "Good to know. Thanks!" - text: "Got it. Thanks for letting me know!" @@ -1355,8 +1515,28 @@ responses: utter_state_attitude: - buttons: + - payload: /confirm_attitude{"attitude":"-10"} + title: "-10 (Bad)" + - payload: /confirm_attitude{"attitude":"-9"} + title: "-9" + - payload: /confirm_attitude{"attitude":"-8"} + title: "-8" + - payload: /confirm_attitude{"attitude":"-7"} + title: "-7" + - payload: /confirm_attitude{"attitude":"-6"} + title: "-6" + - payload: /confirm_attitude{"attitude":"-5"} + title: "-5" + - payload: /confirm_attitude{"attitude":"-4"} + title: "-4" + - payload: /confirm_attitude{"attitude":"-3"} + title: "-3" + - payload: /confirm_attitude{"attitude":"-2"} + title: "-2" + - payload: /confirm_attitude{"attitude":"-1"} + title: "-1" - payload: /confirm_attitude{"attitude":"0"} - title: "0 (Very useless)" + title: "0" - payload: /confirm_attitude{"attitude":"1"} title: "1" - payload: /confirm_attitude{"attitude":"2"} @@ -1376,14 +1556,15 @@ responses: - payload: /confirm_attitude{"attitude":"9"} title: "9" - payload: /confirm_attitude{"attitude":"10"} - title: "10 (Very useful)" - text: "Please complete the following statement: 'For me, planning walks for the next 3 months would be ...'" + title: "10 (Good)" + text: "Please complete the following statement: 'Making plans for taking walks is...'" + utter_attitude_repeat: - text: "So your attitude is {attitude}." utter_thank_for_attitude: - - text: "That is good to know. Thank you." + - text: "That is good to know. Thank you!" - text: "Thanks for telling me!" utter_input_too_short_takeaway_1: @@ -1405,7 +1586,7 @@ responses: - text: "It seems like your answer is a bit too short... Please type at least 3 words. The question was: How do you think planning can help you do this?" utter_explain_planning_1: - - text: "Thanks for telling me! \n I think it would be useful to look again at more ways in which planning can help you be more physically active by walking." + - text: "Thanks for telling me! \n I think it would be useful to look at ways in which planning can help you be more physically active by walking." utter_explain_planning_2: - buttons: @@ -1428,20 +1609,6 @@ responses: utter_thank_for_planning_explanation: - text: "I see. Thanks for sharing. Here is what I think planning can help with." - # utter_explain_planning_5: - # - text: "By creating a plan that is consistent across different weeks, it will be easier for you to form a habit of when you should go for a walk." - - # utter_explain_planning_6: - # - text: "Besides this, the plan shows you what you will be able to achieve in the end, so you have an idea of the goal you are working towards." - - # utter_explain_planning_7: - # - text: "Planning can also help you identify and deal with obstacles that might prevent you from going on your walks." - - # utter_explain_planning_8: - # - text: "By anticipating these obstacles and devising strategies for overcoming them ahead of time, it will be easier to deal with them when they happen." - - - utter_identify_barriers_1: - text: "That is good to know. Thank you. \n I think it might be useful to think of some barriers, or things that might stop or prevent you from taking walks regularly." @@ -1465,30 +1632,16 @@ responses: text: "Which of these do you think is the biggest barrier for you?" utter_identify_barriers_4: - - text: "I see. Thank you for informing me. Would you mind briefly describing your barrier to me in the chat?" + - text: "I see. Thank you for informing me! Would you mind briefly describing your barrier to me in the chat?" utter_identify_barriers_5: - - text: "Thank you for telling me. I'll try to think a way to overcome your barrier. In the meantime..." - - # utter_identify_barriers_7: - # - text: "Maybe you don't have shoes that are comfortable to take longer walks in." - - # utter_identify_barriers_8: - # - text: "It could also be that you have young children to take care of and that leaves you with little time." - - # utter_identify_barriers_9: - # - text: "All of these are barriers which could prevent you from taking a walk. Think if these apply to you specifically." - - # utter_identify_barriers_10: - # - text: "You can also try to come up with some things that you know have been barriers in the past. Think about which of these barriers apply to you and let me know when you are ready to proceed." - - + - text: "Thank you for telling me!" utter_deal_with_barriers_1: - - text: "Thank you for telling me. \n We've previously thought of barriers which might prevent you from taking walks regularly." + - text: "Thank you for telling me! \n We've previously thought of barriers which might prevent you from taking walks regularly." utter_deal_with_barriers_2: - - text: "Now, let's try to think how you could overcome those barriers." + - text: "Now, let's try to think of how you could overcome those barriers." utter_deal_with_barriers_3: - text: "Keep in mind that, while I can provide suggestions, it's really up to you to come up with the solution that works best for you." @@ -1520,18 +1673,6 @@ responses: utter_deal_with_barriers_9: - text: "Great! Thank you for sharing!" - # utter_deal_with_barriers_10: - # - text: "In terms of energy, think back to when we talked about when you feel most energetic during the day. Aim to schedule walks at those times, since it will be easier for you that way." - - # utter_deal_with_barriers_11: - # - text: "Try to go to the gym when it's a bit quieter if you don't like it when other people can see you doing physical activity. It is not absolutely necessary to go to a gym, especially if you're only taking walks. Try walking in a park nearby or simply on the sidewalk." - - # utter_deal_with_barriers_12: - # - text: "Another common barrier is not having the right equipment. For walking in particular, the only thing you really need are shoes that are comfortable for you, so putting aside some money for that can be a relatively simple strategy." - - # utter_deal_with_barriers_13: - # - text: "If you have children to take care of, it might be a good idea to take them on regular walks with you. That way you can fulfill your family obligations and make progress towards your goal." - utter_show_testimonials_1: - text: "Thanks for letting me know. \n Seeing other people make plans and see how they succeed with regards to their goals is something that might help you." @@ -1575,7 +1716,7 @@ responses: - text: "Okay, that's good!" utter_changes_to_plan_1: - - text: "I see. Thank you for informing me. \n I propose we take another look at the plan." + - text: "I see. Thank you for informing me! \n I propose we take another look at the plan." utter_changes_to_plan_intermediary: - text: "Maybe considering our discussion so far, you realised that you want to change some of the times when you have to go for walks. If you do not want to change anything, please just click the button titled 'I am done adjusting the plan'" @@ -1620,8 +1761,28 @@ responses: utter_satisfaction: - buttons: + - payload: /confirm_satisfaction{"satisfaction":"-10"} + title: "-10 (I am very dissatisfied)" + - payload: /confirm_satisfaction{"satisfaction":"-9"} + title: "-9" + - payload: /confirm_satisfaction{"satisfaction":"-8"} + title: "-8" + - payload: /confirm_satisfaction{"satisfaction":"-7"} + title: "-7" + - payload: /confirm_satisfaction{"satisfaction":"-6"} + title: "-6" + - payload: /confirm_satisfaction{"satisfaction":"-5"} + title: "-5" + - payload: /confirm_satisfaction{"satisfaction":"-4"} + title: "-4" + - payload: /confirm_satisfaction{"satisfaction":"-3"} + title: "-3" + - payload: /confirm_satisfaction{"satisfaction":"-2"} + title: "-2" + - payload: /confirm_satisfaction{"satisfaction":"-1"} + title: "-1" - payload: /confirm_satisfaction{"satisfaction":"0"} - title: "0 (I am very dissatisfied)" + title: "0" - payload: /confirm_satisfaction{"satisfaction":"1"} title: "1" - payload: /confirm_satisfaction{"satisfaction":"2"} @@ -1642,12 +1803,32 @@ responses: title: "9" - payload: /confirm_satisfaction{"satisfaction":"10"} title: "10 (I am very satisfied)" - text: "How satisfied were you with the dialogue?" + text: "How satisfied or dissatisfied were you with the dialogue?" utter_commitment_1: - buttons: + - payload: /confirm_commitment_1{"commitment_1":"-10"} + title: "-10 (I strongly disagree)" + - payload: /confirm_commitment_1{"commitment_1":"-9"} + title: "-9" + - payload: /confirm_commitment_1{"commitment_1":"-8"} + title: "-8" + - payload: /confirm_commitment_1{"commitment_1":"-7"} + title: "-7" + - payload: /confirm_commitment_1{"commitment_1":"-6"} + title: "-6" + - payload: /confirm_commitment_1{"commitment_1":"-5"} + title: "-5" + - payload: /confirm_commitment_1{"commitment_1":"-4"} + title: "-4" + - payload: /confirm_commitment_1{"commitment_1":"-3"} + title: "-3" + - payload: /confirm_commitment_1{"commitment_1":"-2"} + title: "-2" + - payload: /confirm_commitment_1{"commitment_1":"-1"} + title: "-1" - payload: /confirm_commitment_1{"commitment_1":"0"} - title: "0 (I strongly disagree)" + title: "0" - payload: /confirm_commitment_1{"commitment_1":"1"} title: "1" - payload: /confirm_commitment_1{"commitment_1":"2"} @@ -1668,12 +1849,32 @@ responses: title: "9" - payload: /confirm_commitment_1{"commitment_1":"10"} title: "10 (I strongly agree)" - text: "How much do you agree with the following statement: 'I am committing to being physically active by following the first two weeks of the plan, no matter what feels uncomfortable or challenging about that'?" + text: "How much do you agree or disagree with the following statement: 'I am committing to being physically active by following the first two weeks of the plan, no matter what feels uncomfortable or challenging about that'?" utter_commitment_f: - buttons: + - payload: /confirm_commitment_f{"commitment_f":"-10"} + title: "-10 (I strongly disagree)" + - payload: /confirm_commitment_f{"commitment_f":"-9"} + title: "-9" + - payload: /confirm_commitment_f{"commitment_f":"-8"} + title: "-8" + - payload: /confirm_commitment_f{"commitment_f":"-7"} + title: "-7" + - payload: /confirm_commitment_f{"commitment_f":"-6"} + title: "-6" + - payload: /confirm_commitment_f{"commitment_f":"-5"} + title: "-5" + - payload: /confirm_commitment_f{"commitment_f":"-4"} + title: "-4" + - payload: /confirm_commitment_f{"commitment_f":"-3"} + title: "-3" + - payload: /confirm_commitment_f{"commitment_f":"-2"} + title: "-2" + - payload: /confirm_commitment_f{"commitment_f":"-1"} + title: "-1" - payload: /confirm_commitment_f{"commitment_f":"0"} - title: "0 (I strongly disagree)" + title: "0" - payload: /confirm_commitment_f{"commitment_f":"1"} title: "1" - payload: /confirm_commitment_f{"commitment_f":"2"} @@ -1694,12 +1895,12 @@ responses: title: "9" - payload: /confirm_commitment_f{"commitment_f":"10"} title: "10 (I strongly agree)" - text: "How much do you agree with the following statement: 'I am committing to being physically active by following the entire plan, no mater what feels uncomfortable or challenging about that'?" + text: "How much do you agree or disagree with the following statement: 'I am committing to being physically active by following the entire plan, no matter what feels uncomfortable or challenging about that'?" utter_confidence_goal: - buttons: - payload: /confirm_confidence_goal{"confidence_goal":"0"} - title: "0 (I am not confident at that I can reach my goal)" + title: "0 (I am not confident at all that I can reach my goal)" - payload: /confirm_confidence_goal{"confidence_goal":"1"} title: "1" - payload: /confirm_confidence_goal{"confidence_goal":"2"} @@ -1719,20 +1920,22 @@ responses: - payload: /confirm_confidence_goal{"confidence_goal":"9"} title: "9" - payload: /confirm_confidence_goal{"confidence_goal":"10"} - title: "10 (I am sure that I can reach my goal)" + title: "10 (I am very confident that I can reach my goal)" text: "How confident are you that you can reach your goal of being able to take {goal} steps per day in six months from now?" utter_thank: - text: "Thank you for all your answers and for your time!" + utter_prolific_link: + - text: "Here is your completion link: ... ." + ### End session - utter_email_reminder: - - text: "That's all for this session. I'll be sending you a message on Prolific right after this session to confirm that you have completed everything." + utter_post: + - text: "That's all for this session. You will soon get invited on Prolific to complete the post questionnaire." + - utter_prolific_link: - - text: "Alright! Then here is your completion link: ... ." utter_goodbye: @@ -1751,7 +1954,7 @@ responses: utter_default_close_session: - - text: "Please close this window and contact the researcher on prolific." + - text: "Please close this window and contact the researcher on Prolific." utter_error_close_session: @@ -1759,7 +1962,7 @@ responses: session_config: - session_expiration_time: 33 # these are minutes (33 is max. time in Prolific for 6-minute study) + session_expiration_time: 56 # these are minutes (56 is max. time in Prolific for 15-minute study) carry_over_slots_to_new_session: false @@ -1782,3 +1985,4 @@ actions: - action_save_action - action_save_goal_plans_and_reward - action_rearrange_db +- action_check_dialogue_done \ No newline at end of file diff --git a/backend/models/core-20230510-213224-sophisticated-tree.tar.gz b/backend/models/core-20230510-213224-sophisticated-tree.tar.gz deleted file mode 100644 index d318300..0000000 Binary files a/backend/models/core-20230510-213224-sophisticated-tree.tar.gz and /dev/null differ diff --git a/backend/models/core-20230608-101919-light-core.tar.gz b/backend/models/core-20230608-101919-light-core.tar.gz new file mode 100644 index 0000000..33921c8 Binary files /dev/null and b/backend/models/core-20230608-101919-light-core.tar.gz differ diff --git a/db/rasadb.sql b/db/rasadb.sql index 04af3b9..f1fafe5 100644 --- a/db/rasadb.sql +++ b/db/rasadb.sql @@ -3,7 +3,7 @@ use db; CREATE TABLE sessiondata(prolific_id TEXT, time DATETIME, event TEXT); -CREATE TABLE users(prolific_id TEXT, time DATETIME, goal TEXT, plan_1 TEXT, plan_2 TEXT, plan_3 TEXT, reward TEXT); +CREATE TABLE users(prolific_id TEXT, time DATETIME, goal TEXT, plan_1 TEXT, plan_2 TEXT, plan_3 TEXT, reward TEXT, testimonial_1 TEXT, takeaway_1 TEXT, testimonial_2 TEXT, takeaway_2 TEXT, identified_barrier TEXT, barrier_description TEXT, barrier_strategy_1 TEXT, barrier_strategy_2 TEXT, planning_relevance TEXT, planning_importance_explanation TEXT); CREATE TABLE state_action_state(prolific_id TEXT, time DATETIME, state_before TEXT, action TEXT, state_after TEXT); diff --git a/frontend/index.html b/frontend/index.html index 6e842e0..7ac7b5d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -35,9 +35,8 @@ -
- -
+
+
@@ -50,20 +49,20 @@
- - - - - - - - - - - - - - + + + + + + + + + + + + + +
Week 1MondayTuesdayWednesdayThursdayFridaySaturdaySunday
Morning (6:00 - 10:00)
Midday (10:00 - 14:00)
Afternoon (14:00 - 18:00)
Evening (18:00 - 22:00)
Week 2MondayTuesdayWednesdayThursdayFridaySaturdaySunday
Morning (6:00 - 10:00)
Midday (10:00 - 14:00)
Afternoon (14:00 - 18:00)
Evening (18:00 - 22:00)
Week 3
Week 4
Month 2
Month 3
Week 1MondayTuesdayWednesdayThursdayFridaySaturdaySunday
Morning (6:00 - 10:00)
Midday (10:00 - 14:00)
Afternoon (14:00 - 18:00)
Evening (18:00 - 22:00)
Week 2MondayTuesdayWednesdayThursdayFridaySaturdaySunday
Morning (6:00 - 10:00)
Midday (10:00 - 14:00)
Afternoon (14:00 - 18:00)
Evening (18:00 - 22:00)
Week 3
Week 4
Month 2
Month 3
I'm done adjusting the plan.
@@ -71,11 +70,11 @@
- - - - - + + + + +
MondayTuesdayWednesdayThursdayFridaySaturdaySunday
Morning (6:00 - 10:00)
Midday (10:00 - 14:00)
Afternoon (14:00 - 18:00)
Evening (18:00 - 22:00)
MondayTuesdayWednesdayThursdayFridaySaturdaySunday
Morning (6:00 - 10:00)
Midday (10:00 - 14:00)
Afternoon (14:00 - 18:00)
Evening (18:00 - 22:00)
I'm done selecting time slots when I am free. @@ -84,18 +83,18 @@
- - - - - - - - - - - - + + + + + + + + + + + +
WeekdaysMorning (6:00 - 10:00)Midday (10:00 - 14:00)Afternoon (14:00 - 18:00)Evening (18:00 - 22:00)
None at all
Less than usual
An average amount
More than average
Much more than average
WeekendsMorning (6:00 - 10:00)Midday (10:00 - 14:00)Afternoon (14:00 - 18:00)Evening (18:00 - 22:00)
None at all
Less than usual
An average amount
More than average
Much more than average
WeekdaysMorning (6:00 - 10:00)Midday (10:00 - 14:00)Afternoon (14:00 - 18:00)Evening (18:00 - 22:00)
None at all
Less than usual
An average amount
More than average
Much more than average
WeekendsMorning (6:00 - 10:00)Midday (10:00 - 14:00)Afternoon (14:00 - 18:00)Evening (18:00 - 22:00)
None at all
Less than usual
An average amount
More than average
Much more than average
I'm done specifying my energy levels.
diff --git a/frontend/static/css/style.css b/frontend/static/css/style.css index d7fff74..4add9eb 100644 --- a/frontend/static/css/style.css +++ b/frontend/static/css/style.css @@ -34,7 +34,6 @@ body { } .chats { - /* display: none; */ /* Set height of chat area within window in fullscreen mode*/ height: 81%; padding: 2px; @@ -65,11 +64,21 @@ body { } .plan{ + display: none; + top: 3%; + position: relative; width: 800px; height: 500px; margin: 0 auto; } +.plan_after_week_2{ + display: none; + width: 800px; + height: 150px; + margin: 0 auto; +} + .timeslots_table{ display: none; width: 57%; @@ -107,9 +116,6 @@ body { div.chats::-webkit-scrollbar { overflow-y: hidden; width: 0px; - /* remove scrollbar space / - background: transparent; - / optional: just make scrollbar invisible */ } .clearfix { @@ -118,12 +124,10 @@ div.chats::-webkit-scrollbar { } .botAvatar { -/* border-radius: 50%; */ width: 1.5em; height: 1.5em; float: left; margin-left: 5px; - /* border: 2px solid #2ed59f ; */ } .botMsg { @@ -179,12 +183,10 @@ textarea-webkit-scrollbar { .userAvatar { animation: animateElement linear 0.3s; animation-iteration-count: 1; - border-radius: 50%; width: 1.5em; height: 1.5em; float: right; margin-right: 5px; - /* border: 2px solid #2ed59f ; */ } .usrInput { @@ -197,7 +199,6 @@ textarea-webkit-scrollbar { } .keypad { - /* display: none; */ background: white; height: 50px; position: absolute; @@ -227,6 +228,8 @@ textarea-webkit-scrollbar { width: 250px; text-align: center; margin: 10 auto; + position: relative; + top: 5%; cursor: pointer; border: 2px solid #000000; background-color: #2ed59f61 @@ -313,23 +316,26 @@ textarea-webkit-scrollbar { } } -@keyframes border-flicker-green { +@keyframes border-flicker-yellow { 0% { - box-shadow: 5px 5px 20px #09ff00; + box-shadow: 5px 5px 20px #F7F97A; } - 25% { - box-shadow: 5px 5px 20px #09ff00; + 20% { + box-shadow: 5px 5px 20px #F7F97A; } - 50% { - box-shadow: 5px 5px 50px #09ff00; + 40% { + box-shadow: 5px 5px 50px white; + } + 60% { + box-shadow: 5px 5px 50px white; } - 75% { - box-shadow: 5px 5px 20px #09ff00; + 80% { + box-shadow: 5px 5px 20px #F7F97A; } 100% { - box-shadow: 5px 5px 20px #09ff00; + box-shadow: 5px 5px 20px #F7F97A; } - } +} /*========== css related to chats elements============= */ @@ -482,9 +488,6 @@ div.content::-webkit-scrollbar { div.content_data::-webkit-scrollbar { overflow-y: hidden; width: 0px; - /* remove scrollbar space / - background: transparent; - / optional: just make scrollbar invisible */ } @@ -527,6 +530,20 @@ div.content_data::-webkit-scrollbar { padding: 10px; } +.menuCustom { + padding: 2px; + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.menuCustom { + padding: 2px; + display: flex; + flex-wrap: wrap; + justify-content: center; +} + .menuChips { display: block; background: #2ed59f; @@ -541,6 +558,20 @@ div.content_data::-webkit-scrollbar { word-wrap: break-word; } +.menuChipsCustom { + display: block; + background: #2ed59f; + color: #fff; + padding:7px; + text-align: center; + margin-bottom: 1px; + cursor: pointer; + border-radius: 50px; + font-size: 13px; + margin: 2px; + word-wrap: break-word; +} + /* cards carousels */ @@ -693,9 +724,7 @@ div.content_data::-webkit-scrollbar { display: inline-block; margin-bottom: 1px; font-size: 15px; - /* font-weight: 600; */ padding: 5px; - /* color: #2ed59f; */ color: #ffffff; cursor: pointer; } @@ -906,4 +935,3 @@ input:focus { video:focus { outline: none; } - diff --git a/frontend/static/img/user_picture.png b/frontend/static/img/user_picture.png index 57d3a20..a6dc0a9 100644 Binary files a/frontend/static/img/user_picture.png and b/frontend/static/img/user_picture.png differ diff --git a/frontend/static/js/script.js b/frontend/static/js/script.js index ceb9184..8e0c95e 100644 --- a/frontend/static/js/script.js +++ b/frontend/static/js/script.js @@ -61,7 +61,7 @@ $(".usrInput").on("keyup keypress", function (e) { var message = "/takeaway_1_short"; } else{ - var message = "/confirm_takeaway_1"; + var message = `/confirm_takeaway_1{"takeaway_1":"${text}"}`; } setUserResponse(text); @@ -76,7 +76,7 @@ $(".usrInput").on("keyup keypress", function (e) { var message = "/takeaway_2_short"; } else{ - var message = "/confirm_takeaway_2"; + var message = `/confirm_takeaway_2{"takeaway_2":"${text}"}`; current_takeaway = 99; } @@ -89,10 +89,10 @@ $(".usrInput").on("keyup keypress", function (e) { if(text==first_walk){ - var message = "/first_walk_correct"; + var message = `/first_walk_correct{"first_walk":"${text}"}`; } else{ - var message = "/first_walk_incorrect"; + var message = `/first_walk_incorrect{"first_walk":"${text}"}`; } setUserResponse(text); send(message); @@ -107,10 +107,10 @@ $(".usrInput").on("keyup keypress", function (e) { text = parseFloat(text).toFixed(1); if(text==week_3_time){ - var message = "/week_3_correct"; + var message = `/week_3_correct{"week_3":"${text}"}`; } else{ - var message = "/week_3_incorrect"; + var message = `/week_3_incorrect{"week_3":"${text}"}`; } setUserResponse(text); @@ -128,10 +128,10 @@ $(".usrInput").on("keyup keypress", function (e) { } else{ if(barrier_type != "time" && barrier_type != "energy" && barrier_type != "people" && barrier_type != "equipment" && barrier_type != "family"){ - var message = "/confirm_continue_deal_with_barriers_skip_extra"; + var message = `/confirm_continue_deal_with_barriers_skip_extra{"barrier_strategy_1":"${text}"}`; } else{ - var message = "/confirm_continue_deal_with_barriers_2"; + var message = `/confirm_continue_deal_with_barriers_2{"barrier_strategy_1":"${text}"}`; } } @@ -167,7 +167,7 @@ $(".usrInput").on("keyup keypress", function (e) { var message = "/barrier_repeat_short"; } else{ - var message = "/confirm_continue_deal_with_barriers_3"; + var message = `/confirm_continue_deal_with_barriers_3{"barrier_strategy_2":"${text}"}`; } barrier_repeat = false; @@ -184,7 +184,7 @@ $(".usrInput").on("keyup keypress", function (e) { var message = "/planning_short"; } else{ - var message = "/confirm_planning_input"; + var message = `/confirm_planning_input{"planning_importance_explanation":"${text}"}`; } planning = false; @@ -272,8 +272,6 @@ function setBotResponse(response) { //display bot response after the number of miliseconds caputred by the variable 'delay_first_message' var delay_first_message = 500; if (response.length >=1) { - // delay_first_message = Math.min(Math.max(response[0].text.length * 45, 800), 5000); - delay_first_message = 20; } setTimeout(function () { hideBotTyping(); @@ -293,16 +291,22 @@ function setBotResponse(response) { var response_text = response[0].text.split("\n") for (j = 0; j < response_text.length; j++){ - if(response_text[j].includes("I see, thank you for letting me know!")){ - $(".timeslots_table").toggle(); - - + if(response_text[j].includes("Great! Take walks regularly and you'll be able to achieve your goal")){ var BotResponse = '

' + response_text[j] + '

'; $(BotResponse).appendTo(".chats").hide().fadeIn(1000); + + setTimeout(()=> { + $(".timeslots_table").toggle(); + } + ,20000); } else if(response_text[j].includes("Now, I want to ask when you have more energy than usual. We could make use of this opportunity to schedule a walk if you are also free at the time.")){ - $(".energy_levels_table").toggle(); + + setTimeout(()=> { + $(".energy_levels_table").toggle(); + } + ,10000); var BotResponse = '

' + response_text[j] + '

'; @@ -350,10 +354,18 @@ function setBotResponse(response) { document.getElementById("month_3").innerHTML = "Walking for up to " + month_3 + " hours per week, distributed across 6 time slots each week"; - $(".plan_table").toggle(); + $(".plan_table").css("display","block"); + + $(".plan").css("display","table"); number_plan = 1; } + else if(response_text[j].includes("That's right, your first walk is on")){ + var BotResponse = '

' + response_text[j] + '

'; + $(BotResponse).appendTo(".chats").hide().fadeIn(1000); + + $(".plan_after_week_2").css("display", "table-row"); + } else if(response_text[j].includes("this is a message for javascript: enable the buttons")){ var button = document.getElementById("submit_plan_button"); @@ -376,80 +388,110 @@ function setBotResponse(response) { days.forEach(element => document.getElementById(element).classList.add("toggleable")); - button.style.display = "table"; + setTimeout(function(){ + button.style.display = "table"; + } + ,35000); } else if(response_text[j].includes("What can you take away from this example for yourself? Please type this in the chat.") || response_text[j].includes("The question was: What can you take away from this example for yourself?")){ var BotResponse = '

' + response_text[j] + '

'; $(BotResponse).appendTo(".chats").hide().fadeIn(1000); - - $('.usrInput').attr("disabled",false); - $(".usrInput").prop('placeholder', "Type something..."); - current_takeaway = 1; + setTimeout(function(){ + $('.usrInput').attr("disabled",false); + $(".usrInput").prop('placeholder', "Type something..."); + current_takeaway = 1; + blink_and_select(); + } + ,5000); } else if(response_text[j].includes("How about this example? What can you take away for yourself? Please type this in the chat.") || response_text[j].includes("The question was: What can you take away from this second example for yourself?")){ var BotResponse = '

' + response_text[j] + '

'; $(BotResponse).appendTo(".chats").hide().fadeIn(1000); - - $('.usrInput').attr("disabled",false); - $(".usrInput").prop('placeholder', "Type something..."); - current_takeaway = 2; + setTimeout(function(){ + $('.usrInput').attr("disabled",false); + $(".usrInput").prop('placeholder', "Type something..."); + current_takeaway = 2; + blink_and_select(); + } + ,5000); } else if(response_text[j].includes("Please type your answer as a number with one decimal point.")){ var BotResponse = '

' + response_text[j] + '

'; $(BotResponse).appendTo(".chats").hide().fadeIn(1000); - - $('.usrInput').attr("disabled",false); - $(".usrInput").prop('placeholder', "Type something..."); - check_week_3 = true; + setTimeout(function(){ + $('.usrInput').attr("disabled",false); + $(".usrInput").prop('placeholder', "Type something..."); + check_week_3 = true; + blink_and_select(); + } + ,5000); } else if(response_text[j].includes("You can see the first two weeks planned in detail.")){ var BotResponse = '

' + response_text[j] + '

'; $(BotResponse).appendTo(".chats").hide().fadeIn(1000); - - $('.usrInput').attr("disabled",false); - $(".usrInput").prop('placeholder', "Type something..."); - check_first_walk = true; + setTimeout(function(){ + $('.usrInput').attr("disabled",false); + $(".usrInput").prop('placeholder', "Type something..."); + check_first_walk = true; + blink_and_select(); + } + ,5000); } else if(response_text[j].includes("User barrier: ")){ - - $('.usrInput').attr("disabled",false); - $(".usrInput").prop('placeholder', "Type something..."); - barrier_strategy = true; barrier_type = response_text[j].split("User barrier: ")[1]; + setTimeout(function(){ + $('.usrInput').attr("disabled",false); + $(".usrInput").prop('placeholder', "Type something..."); + barrier_strategy = true; + blink_and_select(); + } + ,5000); } else if(response_text[j].includes("The question was: How can you overcome this barrier?")){ var BotResponse = '

' + response_text[j] + '

'; $(BotResponse).appendTo(".chats").hide().fadeIn(1000); - - $('.usrInput').attr("disabled",false); - $(".usrInput").prop('placeholder', "Type something..."); - barrier_strategy = true; + setTimeout(function(){ + $('.usrInput').attr("disabled",false); + $(".usrInput").prop('placeholder', "Type something..."); + barrier_strategy = true; + blink_and_select(); + } + ,5000); } - else if(response_text[j].includes("I see. Thank you for informing me. Would you mind briefly describing your barrier to me in the chat?") || response_text[j].includes("The question was: Would you mind briefly describing your barrier to me in the chat?")){ + else if(response_text[j].includes("Would you mind briefly describing your barrier to me in the chat?") || response_text[j].includes("The question was: Would you mind briefly describing your barrier to me in the chat?")){ var BotResponse = '

' + response_text[j] + '

'; $(BotResponse).appendTo(".chats").hide().fadeIn(1000); - - $('.usrInput').attr("disabled",false); - $(".usrInput").prop('placeholder', "Type something..."); - identified_barrier = true; + setTimeout(function(){ + $('.usrInput').attr("disabled",false); + $(".usrInput").prop('placeholder', "Type something..."); + identified_barrier = true; + blink_and_select(); + } + ,5000); } else if(response_text[j].includes("Okay! Now, you have your approach to this barrier. Here is a strategy I thought about.") || response_text[j].includes("The question was: How can you overcome this barrier after having read my suggestion?")){ var BotResponse = '

' + response_text[j] + '

'; $(BotResponse).appendTo(".chats").hide().fadeIn(1000); - - $('.usrInput').attr("disabled",false); - $(".usrInput").prop('placeholder', "Type something..."); - barrier_repeat = true; + setTimeout(function(){ + $('.usrInput').attr("disabled",false); + $(".usrInput").prop('placeholder', "Type something..."); + barrier_repeat = true; + blink_and_select(); + } + ,5000); } else if(response_text[j].includes("Good choice!") || response_text[j].includes("The question was: How do you think planning can help you do this?")){ var BotResponse = '

' + response_text[j] + '

'; $(BotResponse).appendTo(".chats").hide().fadeIn(1000); - - $('.usrInput').attr("disabled",false); - $(".usrInput").prop('placeholder', "Type something..."); - planning = true; + setTimeout(function(){ + $('.usrInput').attr("disabled",false); + $(".usrInput").prop('placeholder', "Type something..."); + planning = true; + blink_and_select(); + } + ,5000); } // otherwise, display the message else{ @@ -473,7 +515,6 @@ function setBotResponse(response) { if (response.length > 1){ //show typing symbol again var delay_typing = 600 + delay_first_message; - // var delay_typing = 20; setTimeout(function () { showBotTyping(); }, delay_typing) @@ -521,15 +562,17 @@ function doScaledTimeout(i, response, summed_timeout) { }, summed_timeout); } -function blink() { + +function blink_and_select() { + document.getElementById("userInput").focus(); + document.getElementById("userInput").select(); + var f = document.getElementById('keypad'); + f.style.animation = 'border-flicker-yellow 3s ease-out'; setTimeout(function() { - f.style.boxShadow = '5px 5px 50px #09ff00'; - setTimeout(function() { - f.style.boxShadow = 'none'; - }, 1000); - }, 1000); - } + f.style.animation = 'none'; + }, 5000); + //====================================== Toggle chatbot ======================================= @@ -612,22 +655,25 @@ function select_energy(clicked_id){ var time_slot = document.getElementById(clicked_id); - time_slot.style.backgroundColor = "#82e876"; - if(clicked_id.includes("0")){ - time_slot.innerHTML = "None at all" + time_slot.innerHTML = "None at all"; + time_slot.style.backgroundColor = "#F7F97A"; } else if (clicked_id.includes("1")){ - time_slot.innerHTML = "Less than usual" + time_slot.innerHTML = "Less than usual"; + time_slot.style.backgroundColor = "#B1F97D"; } else if (clicked_id.includes("2")){ - time_slot.innerHTML = "An average amount" + time_slot.innerHTML = "An average amount"; + time_slot.style.backgroundColor = "#82E876"; } else if (clicked_id.includes("3")){ - time_slot.innerHTML = "More than average" + time_slot.innerHTML = "More than average"; + time_slot.style.backgroundColor = "#21E4AE"; } else if (clicked_id.includes("4")){ - time_slot.innerHTML = "Much more than average" + time_slot.innerHTML = "Much more than average"; + time_slot.style.backgroundColor = "#4EB3F5"; } } @@ -710,6 +756,11 @@ function check_selected_timeslots(){ } } +function day_only(id){ + const day = id.split('_')[0]; + return day; +} + function check_selected_timeslots_initial(){ var button = document.getElementById("submit_timeslots_button"); @@ -725,21 +776,28 @@ function check_selected_timeslots_initial(){ "sunday_morning_slot", "sunday_midday_slot", "sunday_afternoon_slot", "sunday_evening_slot" ] - var count = 0; - days.forEach(element_id => count+= check_inner_HTML(element_id)); - var selected_slots = []; - days.forEach(element_id => selected_slots.push(slots_selected_initial(element_id))); + var selected_slots_day_only = []; + + days.forEach(function(element_id) { + count+= check_inner_HTML(element_id); + selected_slots.push(slots_selected_initial(element_id)); + }); var selected_slots = selected_slots.filter(function (el) { return el != ""; }); + selected_slots.forEach(function(element_id) { + if (!selected_slots_day_only.includes(day_only(element_id))) { + selected_slots_day_only.push(day_only(element_id)) + } + }); - if(count >= 4){ + if(selected_slots_day_only.length >= 4 || selected_slots_day_only.length == 3 && count >=4){ button.style.display = "none"; table.style.display = "none"; @@ -759,7 +817,7 @@ function check_selected_timeslots_initial(){ } else{ - window.alert("Please select at least four time slots."); + window.alert("Please select at least four time slots and at least four different days."); } } @@ -899,20 +957,50 @@ function addSuggestion(textToAdd) { setTimeout(function () { $(".usrInput").prop('placeholder', "Use one of the buttons to answer."); + var suggestions = textToAdd; var suggLength = textToAdd.length; - $('
').appendTo(".chats").hide().fadeIn(1000); - // Loop through suggestions - for (i = 0; i < suggLength; i++) { - $('").appendTo(".menu"); + + $('
').appendTo(".chats").hide().fadeIn(1000); + + + if (suggestions[0].title == "Happy" || suggestions[0].title == "-10 (I think it can hinder me very much)" || suggestions[0].title == "-10 (Bad)" || suggestions[0].title == "-10 (I am very dissatisfied)" || suggestions[0].title == "-10 (I strongly disagree)"){ + + + for (i = 0; i < suggLength; i++) { + $('").appendTo(".menuCustom"); + } } + else{ + // Loop through suggestions + for (i = 0; i < suggLength; i++) { + $('").appendTo(".menu"); + } + } + + + scrollToBottomOfResults(); }, 1000); } // on click of suggestions, get the value and send to rasa $(document).on("click", ".menu .menuChips", function () { - // $('.usrInput').attr("disabled",false); + $(".usrInput").prop('placeholder', "Use the buttons to communicate with Jamie."); + var text = this.innerText; + var payload = this.getAttribute('data-payload'); + console.log("payload: ", this.getAttribute('data-payload')) + setUserResponse(text); + send(payload); + + //delete the suggestions once user click on it + $(".suggestions").remove(); + +}); + + +// on click of suggestions, get the value and send to rasa +$(document).on("click", ".menuCustom .menuChipsCustom", function () { $(".usrInput").prop('placeholder', "Use the buttons to communicate with Jamie."); var text = this.innerText; var payload = this.getAttribute('data-payload');