diff --git a/.gitignore b/.gitignore index 38d4e915..93a7262a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ server/.env server/Accounts/ server/package-lock.json server/q_api/notebooks/.ipynb_checkpoints +server/manual_save .env interface/.env server/toia-dm/.env diff --git a/README.md b/README.md index fa906e4c..660b3018 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # TOIA-2.0 -This is a repository for the TOIA 2.0 System. +This is a repository for TOIA 2.0 System with Soojin's Capstone Project "Elephant in the Room". You can watch a short demo +[here](https://www.youtube.com/watch?v=4EK19DnM4_c), and read the full documentation [here](https://meadow-oboe-807.notion.site/Elephant-in-the-Room-060a99def2bb4b8192e351aac2db52f1). +# Project Specific Setup + +All the code that is written for the project is under the file named shhh... including ShhhPage.js ShhhPlayer.js, shhhsuggestivesearch.js and ShhhPage.css. # Developer Setup @@ -13,24 +17,24 @@ There are three `.env` files. One in the root directory, one in the `/interface` EXPRESS_PORT=3001 DM_PORT=5001 - + ENVIRONMENT=development - + EXPRESS_HOST=http://localhost DM_ROUTE=http://toia-dm:5001/dialogue_manager Q_API_ROUTE=http://q_api:5000/generateNextQ - + DB_CONNECTION=mysql DB_DATABASE=toia - + DB_HOST=mysql DB_USERNAME=root DB_PASSWORD= - + GC_BUCKET= GOOGLE_SPEECH_API_CREDENTIALS_FILE=/speech_to_text/toia-capstone-2021-b944d1cc65aa.json GOOGLE_CLOUD_STORE_CREDENTIALS_FILE=/toia-capstone-2021-a17d9d7dd482.json - + OPENAI_API_KEY= 1. Set the `DB_PASSWORD` @@ -55,11 +59,11 @@ Note: It's probably a good idea to place Google cloud related environment variab ## Database Migration -1. Create a folder called `Accounts` in the server folder, if it does not exist. +1. Create a folder called `Accounts` in the server folder, if it does not exist. 2. Copy the video folders to the `Accounts` folder provided by admin. 3. Navigate to [localhost:8080](localhost:8080) with the username and password provided in `.env` in the root folder. 4. Drop all the tables in toia (if needed backup the current toia db using export). -5. Import the database sql file into the toia table. +5. Import the database sql file into the toia table. ## Running the app @@ -72,10 +76,10 @@ Make sure you have installed docker and the docker daemon is running. Making Changes Under Development Mode - The docker-compose file is setup such that react and nodejs changes are reflected as soon as you change the files in `/interface` or `/server` -- If you change anything inside `/server/q_api` or `/server/toia-dm`, you have to restart that particular container. +- If you change anything inside `/server/q_api` or `/server/toia-dm`, you have to restart that particular container. - By default, the files and database are persistent under development mode (Check volume mounts in `docker-compose-dev.yml`). If you wish to start a fresh environment, run `docker-compose down -v` to make sure all the volumes are purged when shutting down the containers. Then start the containers using the command above. -- If the dialogue manager (toia-dm) shuts down when running in docker, change docker settings to allow more RAM (check [this](https://stackoverflow.com/questions/44533319/how-to-assign-more-memory-to-docker-container)). +- If the dialogue manager (toia-dm) shuts down when running in docker, change docker settings to allow more RAM (check [this](https://stackoverflow.com/questions/44533319/how-to-assign-more-memory-to-docker-container)). #### Production mode -- Change the `ENVIRONMENT` variable in .env file to production and run `docker-compose up` \ No newline at end of file +- Change the `ENVIRONMENT` variable in .env file to production and run `docker-compose up` diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index bb3a186f..b89af7a4 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -46,6 +46,7 @@ services: - ${DM_PORT}:${DM_PORT} volumes: - ./server/toia-dm:/app + tty: true depends_on: mysql: condition: service_healthy @@ -86,7 +87,8 @@ services: - /app/node_modules env_file: - ./.env - - ./interface/.env + environment: + - CHOKIDAR_USEPOLLING=true metabase-sql-wrapper: build: diff --git a/interface/package.json b/interface/package.json index 85adeb19..08e18076 100644 --- a/interface/package.json +++ b/interface/package.json @@ -22,6 +22,7 @@ "firebase": "^8.6.8", "fuse.js": "^6.4.6", "get-blob-duration": "^1.2.0", + "i18next-browser-languagedetector": "^7.0.1", "id3js": "^2.1.1", "install": "^0.13.0", "mp3": "^0.1.0", @@ -32,6 +33,7 @@ "react-bottom-scroll-listener": "^5.0.0", "react-dom": "^17.0.1", "react-elastic-carousel": "^0.11.5", + "react-flag-icon-css": "^1.0.25", "react-notifications": "^1.7.3", "react-router-dom": "^5.2.0", "react-scripts": "4.0.1", @@ -39,7 +41,7 @@ "react-speech-recognition": "^3.8.2", "react-switch": "^6.0.0", "react-webcam": "^5.2.4", - "sass": "^1.49.9", + "sass": "1.39.0", "semantic-ui-css": "^2.4.1", "semantic-ui-react": "^2.0.1", "socket.io": "^4.3.2", diff --git a/interface/src/App.css b/interface/src/App.css index 7f4f8a66..97cb118e 100644 --- a/interface/src/App.css +++ b/interface/src/App.css @@ -116,6 +116,30 @@ color: white; opacity: 0.8; } + + +.nav-shhh_icon { + top: 0%; + left: 50%; + position: absolute; + font-weight: 800; + font-size: 20px; + line-height: 22px; + display: flex; + align-items: center; + justify-content: center; + letter-spacing: -0.015em; + height: 40px; + width: 145px; + border-radius: 3px; + cursor: pointer; + color: white; + opacity: 0.8; +} +.elephant-icon{ + width: 30px; +} + .nav-login_icon { top: 0%; left: 90%; @@ -141,7 +165,11 @@ opacity: 0.8; } -.nav-about_icon:hover { +.nav-shhh_icon:hover{ + opacity: 1; +} + +.nav-about_icon:hover{ opacity: 1; } .nav-talk_icon:hover { @@ -154,6 +182,55 @@ opacity: 1; } +/* Dropdown Button */ +.nav-dropbtn { + background-color: transparent; + color: white; + width: 30px; + text-align: center; + font-size: 16px; + border: none; +} + +/* The container
- needed to position the dropdown content */ +.nav-dropdown { + top: 0%; + left: 0%; + position: relative; + display: inline-block; +} + +/* Dropdown Content (Hidden by Default) */ +.nav-dropdown-content { + display: none; + position: absolute; + background-color: #f1f1f1; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +/* Links inside the dropdown */ +.nav-dropdown-content a { + color: black; + width: 30px; + height: 20px; + text-align: center; + text-decoration: none; + display: block; +} + +.nav-dropdown-content div.header:hover::after { + background-color: transparent; +} +/* Change color of dropdown links on hover */ +.nav-dropdown-content option:hover {background-color: #ddd;} + +/* Show the dropdown menu on hover */ +.nav-dropdown:hover .nav-dropdown-content {display: block;} + +/* Change the background color of the dropdown button when the dropdown content is shown */ +/* .nav-dropdown:hover .nav-dropbtn {background-color: #3e8e41;} */ + /*login popup*/ .login_popup { height: 40vh; @@ -273,3 +350,7 @@ .fade-exit-done { opacity: 0; } + + + + diff --git a/interface/src/images/birth/alison.png b/interface/src/images/birth/alison.png new file mode 100644 index 00000000..8737dbaa Binary files /dev/null and b/interface/src/images/birth/alison.png differ diff --git a/interface/src/images/birth/ashley.png b/interface/src/images/birth/ashley.png new file mode 100644 index 00000000..a354cf25 Binary files /dev/null and b/interface/src/images/birth/ashley.png differ diff --git a/interface/src/images/birth/christian.png b/interface/src/images/birth/christian.png new file mode 100644 index 00000000..e04ff101 Binary files /dev/null and b/interface/src/images/birth/christian.png differ diff --git a/interface/src/images/birth/kaylee.png b/interface/src/images/birth/kaylee.png new file mode 100644 index 00000000..5f12061d Binary files /dev/null and b/interface/src/images/birth/kaylee.png differ diff --git a/interface/src/images/birth/kieona.png b/interface/src/images/birth/kieona.png new file mode 100644 index 00000000..d5fcbce8 Binary files /dev/null and b/interface/src/images/birth/kieona.png differ diff --git a/interface/src/images/birth/krista.png b/interface/src/images/birth/krista.png new file mode 100644 index 00000000..fd79483a Binary files /dev/null and b/interface/src/images/birth/krista.png differ diff --git a/interface/src/images/birth/stephanie.png b/interface/src/images/birth/stephanie.png new file mode 100644 index 00000000..d2f1432f Binary files /dev/null and b/interface/src/images/birth/stephanie.png differ diff --git a/interface/src/images/bishnu.jpg b/interface/src/images/bishnu.jpg new file mode 100644 index 00000000..9ef585d1 Binary files /dev/null and b/interface/src/images/bishnu.jpg differ diff --git a/interface/src/images/child_birth.png b/interface/src/images/child_birth.png new file mode 100644 index 00000000..0fbab979 Binary files /dev/null and b/interface/src/images/child_birth.png differ diff --git a/interface/src/images/death.png b/interface/src/images/death.png new file mode 100644 index 00000000..49475320 Binary files /dev/null and b/interface/src/images/death.png differ diff --git a/interface/src/images/death/brian.png b/interface/src/images/death/brian.png new file mode 100644 index 00000000..af530536 Binary files /dev/null and b/interface/src/images/death/brian.png differ diff --git a/interface/src/images/death/ian.png b/interface/src/images/death/ian.png new file mode 100644 index 00000000..15a08c6d Binary files /dev/null and b/interface/src/images/death/ian.png differ diff --git a/interface/src/images/death/james.png b/interface/src/images/death/james.png new file mode 100644 index 00000000..cd76529d Binary files /dev/null and b/interface/src/images/death/james.png differ diff --git a/interface/src/images/death/jj.png b/interface/src/images/death/jj.png new file mode 100644 index 00000000..f28eb030 Binary files /dev/null and b/interface/src/images/death/jj.png differ diff --git a/interface/src/images/death/kekyong.png b/interface/src/images/death/kekyong.png new file mode 100644 index 00000000..12ab5a13 Binary files /dev/null and b/interface/src/images/death/kekyong.png differ diff --git a/interface/src/images/death/leila.png b/interface/src/images/death/leila.png new file mode 100644 index 00000000..6e494376 Binary files /dev/null and b/interface/src/images/death/leila.png differ diff --git a/interface/src/images/death/norman.png b/interface/src/images/death/norman.png new file mode 100644 index 00000000..90c96ac8 Binary files /dev/null and b/interface/src/images/death/norman.png differ diff --git a/interface/src/images/death/rahma.png b/interface/src/images/death/rahma.png new file mode 100644 index 00000000..2584791e Binary files /dev/null and b/interface/src/images/death/rahma.png differ diff --git a/interface/src/images/death/rina.png b/interface/src/images/death/rina.png new file mode 100644 index 00000000..dcae48a1 Binary files /dev/null and b/interface/src/images/death/rina.png differ diff --git a/interface/src/images/death/sally.png b/interface/src/images/death/sally.png new file mode 100644 index 00000000..48a264d6 Binary files /dev/null and b/interface/src/images/death/sally.png differ diff --git a/interface/src/images/death/serene.png b/interface/src/images/death/serene.png new file mode 100644 index 00000000..e25fe9a0 Binary files /dev/null and b/interface/src/images/death/serene.png differ diff --git a/interface/src/images/elephant-icon.png b/interface/src/images/elephant-icon.png new file mode 100644 index 00000000..4e493d41 Binary files /dev/null and b/interface/src/images/elephant-icon.png differ diff --git a/interface/src/images/elephant.png b/interface/src/images/elephant.png new file mode 100644 index 00000000..f8342550 Binary files /dev/null and b/interface/src/images/elephant.png differ diff --git a/interface/src/images/gautam.jpeg b/interface/src/images/gautam.jpeg new file mode 100644 index 00000000..435c06e3 Binary files /dev/null and b/interface/src/images/gautam.jpeg differ diff --git a/interface/src/images/muhammad.jpeg b/interface/src/images/muhammad.jpeg new file mode 100644 index 00000000..778fda5f Binary files /dev/null and b/interface/src/images/muhammad.jpeg differ diff --git a/interface/src/images/sex.png b/interface/src/images/sex.png new file mode 100644 index 00000000..00a57c24 Binary files /dev/null and b/interface/src/images/sex.png differ diff --git a/interface/src/images/sex/chris.png b/interface/src/images/sex/chris.png new file mode 100644 index 00000000..8a4d0793 Binary files /dev/null and b/interface/src/images/sex/chris.png differ diff --git a/interface/src/images/sex/ellis.png b/interface/src/images/sex/ellis.png new file mode 100644 index 00000000..013d5ce1 Binary files /dev/null and b/interface/src/images/sex/ellis.png differ diff --git a/interface/src/images/sex/kendel.png b/interface/src/images/sex/kendel.png new file mode 100644 index 00000000..f24c7ecb Binary files /dev/null and b/interface/src/images/sex/kendel.png differ diff --git a/interface/src/images/sex/kenny.png b/interface/src/images/sex/kenny.png new file mode 100644 index 00000000..3d26f454 Binary files /dev/null and b/interface/src/images/sex/kenny.png differ diff --git a/interface/src/images/sex/klarissa.png b/interface/src/images/sex/klarissa.png new file mode 100644 index 00000000..15383a19 Binary files /dev/null and b/interface/src/images/sex/klarissa.png differ diff --git a/interface/src/images/sex/lauren.png b/interface/src/images/sex/lauren.png new file mode 100644 index 00000000..2f337920 Binary files /dev/null and b/interface/src/images/sex/lauren.png differ diff --git a/interface/src/images/sex/lucy.png b/interface/src/images/sex/lucy.png new file mode 100644 index 00000000..4b9fbe4c Binary files /dev/null and b/interface/src/images/sex/lucy.png differ diff --git a/interface/src/images/sex/zaldy.png b/interface/src/images/sex/zaldy.png new file mode 100644 index 00000000..8a4ced36 Binary files /dev/null and b/interface/src/images/sex/zaldy.png differ diff --git a/interface/src/images/soojin.jpeg b/interface/src/images/soojin.jpeg new file mode 100644 index 00000000..75e4995d Binary files /dev/null and b/interface/src/images/soojin.jpeg differ diff --git a/interface/src/main.scss b/interface/src/main.scss index 43382f74..f5e8b03a 100644 --- a/interface/src/main.scss +++ b/interface/src/main.scss @@ -11,3 +11,4 @@ @import "pages/Player.css"; @import "pages/SignUpPage.css"; @import "semantic-ui-css/semantic.css"; + diff --git a/interface/src/pages/AboutUsPage.css b/interface/src/pages/AboutUsPage.css index 1aa6c4df..edae150e 100644 --- a/interface/src/pages/AboutUsPage.css +++ b/interface/src/pages/AboutUsPage.css @@ -117,7 +117,7 @@ font-size: 3rem; text-align: left; letter-spacing: -0.015em; - position: absolute; + /* position: absolute; */ /* left: 50%; */ top: 80px; color: black; @@ -129,15 +129,15 @@ } .about-grid { padding: 5px; - overflow-y: auto; + /* overflow-y: auto; */ height: 70%; /* width: 84vw; */ - width: 113.5%; + /* width: 113.5%; */ /* left: 8%; top: 25%; */ left: -6.5%; top: 175px; - position: absolute; + /* position: absolute; */ display: flex; flex-wrap: wrap; flex-direction: row; @@ -153,7 +153,7 @@ font-size: 20px; font-weight: 500; text-align: center; - margin-bottom: 0px; + margin-bottom: 30px !important; } .image-sizing { diff --git a/interface/src/pages/AboutUsPage.js b/interface/src/pages/AboutUsPage.js index ed222a37..a2aa1892 100644 --- a/interface/src/pages/AboutUsPage.js +++ b/interface/src/pages/AboutUsPage.js @@ -1,25 +1,28 @@ -import React, { useState, useEffect } from "react"; +import axios from "axios"; +import React, { useEffect, useState } from "react"; +import { + NotificationContainer, + NotificationManager +} from "react-notifications"; +import { Modal } from "semantic-ui-react"; import submitButton from "../icons/submit-button.svg"; -import sampleVideo from "../icons/sample-video.svg"; import alberto from "../images/alberto.jpeg"; -import wahib from "../images/wahib.jpg"; -import kertu from "../images/kertu.jpg"; +import armaan from "../images/armaan.jpg"; +import bishnu from "../images/bishnu.jpg"; +import camel from "../images/camel.png"; import erin from "../images/erin.jpeg"; -import nizar from "../images/nizar.jpg"; +import gautam from "../images/gautam.jpeg"; import goffredo from "../images/goffredo.jpeg"; -import tyeece from "../images/Tyeece.jpg"; -import armaan from "../images/armaan.jpg"; +import kertu from "../images/kertu.jpg"; +import muhammad from "../images/muhammad.jpeg"; +import nizar from "../images/nizar.jpg"; import nyuad from "../images/nyuad-rb.png"; -import camel from "../images/camel.png"; -import history from "../services/history"; -import { Modal } from "semantic-ui-react"; -import sigDail from "../pdf/SIGDIAL_2021_TOIA_camera_ready_.pdf"; -import axios from "axios"; -import { - NotificationContainer, - NotificationManager, -} from "react-notifications"; +import soojin from "../images/soojin.jpeg"; import toia_logo from "../images/TOIA_Logo.png"; +import tyeece from "../images/Tyeece.jpg"; +import wahib from "../images/wahib.jpg"; +import sigDail from "../pdf/SIGDIAL_2021_TOIA_camera_ready_.pdf"; +import history from "../services/history"; import Tracker from "../utils/tracker"; function AvatarViewPage() { @@ -61,6 +64,10 @@ function AvatarViewPage() { { still: erin, member: "Erin Collins" }, { still: goffredo, member: "Goffredo Puccetti" }, { still: nizar, member: "Nizar Habash" }, + { still: soojin, member: "Soojin Lee" }, + { still: muhammad, member: "Muhammad Ali" }, + { still: gautam, member: "Gautam Dinesh" }, + { still: bishnu, member: "Bishnu Dev" }, ]; const renderTeam = (card, index) => { @@ -205,8 +212,7 @@ function AvatarViewPage() { size="large" style={inlineStyle.modal} open={open} - onClose={() => dispatch(false)} - > + onClose={() => dispatch(false)}>

Welcome Back @@ -243,8 +249,7 @@ function AvatarViewPage() { />
+ onClick={signup}> Don't have an Account? Sign Up
@@ -253,32 +258,27 @@ function AvatarViewPage() {
+ className="nav-toia_icon app-opensans-normal"> TOIA
+ className="nav-about_icon app-monsterrat-black nav-selected"> About Us
+ className="nav-talk_icon app-monsterrat-black "> Talk To TOIA
+ className="nav-my_icon app-monsterrat-black "> My TOIA
+ className="nav-login_icon app-monsterrat-black"> {isLoggedIn ? "Logout" : "Login"}
@@ -320,8 +320,7 @@ function AvatarViewPage() { + className="reference-item"> Github Repo

diff --git a/interface/src/pages/AvatarGardenPage.js b/interface/src/pages/AvatarGardenPage.js index 3a548158..33451fc4 100644 --- a/interface/src/pages/AvatarGardenPage.js +++ b/interface/src/pages/AvatarGardenPage.js @@ -821,7 +821,7 @@ function AvatarGardenPage() { /> {streamSetting} -
+

@@ -935,17 +935,6 @@ function AvatarGardenPage() { }); } - function album_page() { - history.push({ - pathname: "/stream", - state: { - toiaName, - toiaLanguage, - toiaID, - }, - }); - } - function groupSelect() { //function called when the multiple videos are selected if (selectedIndex == null) { @@ -1401,7 +1390,7 @@ function AvatarGardenPage() { className="modal-ans font-class-1" value={playbackVideoAnswer} type={"text"} - onChange={() => {}} + onChange={() => { }} /> diff --git a/interface/src/pages/AvatarLibraryPage.css b/interface/src/pages/AvatarLibraryPage.css index 8276d5b0..58e4dd9c 100644 --- a/interface/src/pages/AvatarLibraryPage.css +++ b/interface/src/pages/AvatarLibraryPage.css @@ -8,6 +8,16 @@ width: 100%; } +.shhh-page { + background-color:#302D2D; + height: 100vh; + margin: 0px; + min-height: 700px; + min-width: 1440px; + position: relative; + width: 100%; +} + .library-page-setup { position: absolute; top: 80px; diff --git a/interface/src/pages/AvatarLibraryPage.js b/interface/src/pages/AvatarLibraryPage.js index 1dd29b68..78261b34 100644 --- a/interface/src/pages/AvatarLibraryPage.js +++ b/interface/src/pages/AvatarLibraryPage.js @@ -120,7 +120,7 @@ function AvatarLibraryPage() { const renderStream = (card, index) => { //cards for streams - + console.log(card) return (
TOIA
+
+ Shhh Icon +
+
{ + if (res.data == -1) { + //alert('Email not found'); + NotificationManager.error("Incorrect e-mail address."); + } else if (res.data == -2) { + NotificationManager.error("Incorrect password."); + } else { + console.log(res.data); + history.push({ + pathname: 'mytoia', + state: { + toiaName: res.data.firstName, + toiaLanguage: res.data.language, + toiaID: res.data.toia_id + } + }); + } + }); + } + + const inlineStyle = { + modal: { + height: '560px', + width: '600px', + } + }; + + function login_modal() { + return ( + dispatch({ type: 'close' })} + > + +

{t("nav_welcome_back")}

+

{t("nav_login_request")}

+
+ + +
+ (input1 = e.target.value)} + name={"email"} + /> + (input2 = e.target.value)} + name={"pass"} + /> + +
{t("nav_signup_request")}
+
+
+
+ ); + } + // style = {{direction: i18n.dir(i18n.language)}} style = {{"unicode-bidi": "plaintext"}} + return ( +
+ + {props.showLoginModal ? login_modal() : ''} + +
+ + + +
+ {t("nav_toia")} +
+
+ {t("shhh")} +
+
+ {t("nav_about_us")} +
+
+ {t("nav_talk_to_toia")} +
+
+ {t("nav_my_toia")} +
+
+ {props.isLoggedIn ? t("nav_logout") : t("nav_login")} +
+
+
+ ); + +} + + + +export default NavBar; \ No newline at end of file diff --git a/interface/src/pages/Player.css b/interface/src/pages/Player.css index 80ed5167..97503c6b 100644 --- a/interface/src/pages/Player.css +++ b/interface/src/pages/Player.css @@ -138,12 +138,25 @@ color: #49769c; } -.cards-suggestion-header { +.player-cards-wrapper { + display: flex; + flex-direction: column; position: absolute; left: 75.5%; + top: 3%; +} + +.player-cards-wrapper > div { + margin-bottom: 1rem; + } -.card-1 { +/* .cards-suggestion-header { + position: absolute; + left: 75.5%; +} */ + +/* .card-1 { position: absolute; left: 75.5%; top: 3%; @@ -171,8 +184,95 @@ position: absolute; left: 75.5%; top: 86%; +} */ + +.card-overview { + transition: background 1s !important; +} + +.card-waiting-true { + background: #d7f5ff !important; + box-shadow: 0px 4px 4px rgba(46, 122, 210, 0.25) !important; + border-radius: 5px !important; +} + +.card-width-control { + max-height: 30%; + min-height: 24%; +} +.cursor-disabled { + cursor: not-allowed !important; +} + +.redo-icon { + color: #006a94; +} + +.transcript { + background-color: rgba(126, 124, 124, 0.1); + border: 0; + border-radius: 3px; + height: 41px; + /* left: 39%; */ + left: 1%; + padding: 5px; + position: absolute; + resize: none; + text-align: left; + /* top: 75%; */ + top: 89.75%; + width: 70%; + color: #707070; + font-size: 1.5rem; + padding-top: 25px; + padding-bottom: 25px; +} + +.submit-button { + position: absolute; + top: 93.7%; + left: 57.5%; + width: 13.5%; + height: 7.5%; + font-size: 1.5rem; + color: #49769c; +} + +/* .cards-suggestion-header { + position: absolute; + left: 75.5%; +} */ + +/* .card-1 { + position: absolute; + left: 75.5%; + top: 3%; +} + +.card-2 { + position: absolute; + left: 75.5%; + top: 20%; +} + +.card-3 { + position: absolute; + left: 75.5%; + top: 42%; } +.card-4 { + position: absolute; + left: 75.5%; + top: 64%; +} + +.card-5 { + position: absolute; + left: 75.5%; + top: 86%; +} */ + .card-overview { transition: background 1s !important; } @@ -214,3 +314,59 @@ padding-top: 25px; padding-bottom: 25px; } + +/* Selecting Language for Player */ + +/* Dropdown Button */ +.lang-dropbtn { + background-color: transparent; + color: white; + width: 30px; + text-align: center; + font-size: 16px; + border: none; + } + + /* The container
- needed to position the dropdown content */ + .lang-container { + position: absolute; + top: 75.4%; + left: 65%; + height: 7%; + font-size: 1.5rem; + } + .lang-dropdown { + position: relative; + display: inline-block; + z-index: 10; + } + + /* Dropdown Content (Hidden by Default) */ + .lang-dropdown-content { + display: none; + position: absolute; + background-color: #dad3d3; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1; + } + + /* Links inside the dropdown */ + .lang-dropdown-content a { + color: black; + width: 30px; + height: 20px; + font-size:15px; + text-align: center; + text-decoration: none; + display: block; + } + + .lang-dropdown-content div.header:hover::after { + background-color: transparent; + } + /* Change color of dropdown links on hover */ + .lang-dropdown-content option:hover {background-color: #ddd;} + + /* Show the dropdown menu on hover */ + .lang-dropdown:hover .lang-dropdown-content {display: block;} + \ No newline at end of file diff --git a/interface/src/pages/Player.js b/interface/src/pages/Player.js index fa9ed1b5..7d24639c 100644 --- a/interface/src/pages/Player.js +++ b/interface/src/pages/Player.js @@ -11,7 +11,6 @@ import history from "../services/history"; import SuggestionCard from "../suggestiveSearch/suggestionCards"; import SuggestiveSearch from "../suggestiveSearch/suggestiveSearch"; import speechToTextUtils from "../transcription_utils"; -import PopModal from "../userRating/popModal"; import Tracker from "../utils/tracker"; import VideoPlaybackPlayer from "./sub-components/Videoplayback.Player"; @@ -39,11 +38,7 @@ function Player() { let allSuggestedQuestions = []; const [videoProperties, setVideoProperties] = useState(null); - const [hasRated, setHasRated] = useState(true); // controll the rating field const [transcribedAudio, setTranscribedAudio] = useState(""); - const [ratingParams, setRatingParams] = useState({ - active: false, - }); // suggested questions for cards @@ -147,7 +142,7 @@ function Player() { axios .request(options) - .then(function (response) {}) + .then(function (response) { }) .catch(function (error) { alert("You do not have permission to access this page"); library(); @@ -155,11 +150,6 @@ function Player() { } // if user asks one of the suggested questions function askQuestionFromCard(question) { - if (!hasRated) { - NotificationManager.warning("Please provide a rating", "", 3000); - return; - } - const mode = "CARD"; const oldQuestion = question; @@ -192,11 +182,6 @@ function Player() { setVideoProperties({ key: res.data.url + new Date(), // add timestamp to force video transition animation when the key hasn't changed onEnded: () => { - setRatingParams({ - video_id: res.data.video_id, - question: oldQuestion.question, - }); - setHasRated(false); fetchFiller(); }, source: res.data.url, @@ -226,7 +211,7 @@ function Player() { if (data.isFinal) { question.current = data.alternatives[0].transcript; - speechToTextUtils.stopRecording(); + // speechToTextUtils.stopRecording(); fetchData("VOICE"); } } else { @@ -293,51 +278,7 @@ function Player() { } } - useEffect(() => { - if (hasRated) { - if (interacting.current == "true") { - speechToTextUtils.initRecording(handleDataReceived, error => { - console.error("Error when transcribing", error); - }); - } - } - }, [hasRated]); - - const recordUserRating = function (ratingValue) { - const options = { - method: "POST", - url: "/api/save_player_feedback", - headers: { "Content-Type": "application/json" }, - data: { - ...(history.location.state.toiaID && { - user_id: history.location.state.toiaID, - }), - video_id: ratingParams.video_id, - - question: ratingParams.question, - rating: ratingValue, - }, - }; - - axios - .request(options) - .then(function (response) { - console.log(response.data); - }) - .catch(function (error) { - console.error(error); - }) - .finally(() => { - setHasRated(true); - }); - }; - function fetchData(mode = "UNKNOWN") { - if (!hasRated) { - NotificationManager.warning("Please provide a rating", "", 3000); - return; - } - const oldQuestion = question.current; if (question.current == null || question.current == "") { setFillerPlaying(true); @@ -370,11 +311,6 @@ function Player() { setVideoProperties({ key: res.data.url + new Date(), // add timestamp to force video transition animation when the key hasn't changed onEnded: () => { - setRatingParams({ - video_id: res.data.video_id, - question: oldQuestion, - }); - setHasRated(false); fetchFiller(); }, source: res.data.url, @@ -406,18 +342,10 @@ function Player() { setMicString("STOP ASK BY VOICE"); interacting.current = "true"; - if (hasRated) { - speechToTextUtils.initRecording(handleDataReceived, error => { - console.error("Error when transcribing", error); - // No further action needed, as stream already closes itself on error - }); - } else { - NotificationManager.warning( - "Please provide a rating", - "", - 3000, - ); - } + speechToTextUtils.initRecording(handleDataReceived, error => { + console.error("Error when transcribing", error); + // No further action needed, as stream already closes itself on error + }); } else { speechToTextUtils.stopRecording(); setMicStatus(true); @@ -484,10 +412,6 @@ function Player() { } function submitResponse(e, mode = "UNKNOWN") { - if (!hasRated) { - NotificationManager.warning("Please provide a rating", "", 3000); - return; - } question.current = textInput.current ? textInput.current @@ -518,11 +442,6 @@ function Player() { setVideoProperties({ key: res.data.url + new Date(), // add timestamp to force video transition animation when the key hasn't changed onEnded: () => { - setRatingParams({ - video_id: res.data.video_id, - question: oldQuestion, - }); - setHasRated(false); fetchFiller(); }, source: res.data.url, @@ -669,8 +588,7 @@ function Player() { size="large" style={inlineStyle.modal} open={open} - onClose={() => dispatch({ type: "close" })} - > + onClose={() => dispatch({ type: "close" })}>

Welcome Back @@ -707,44 +625,37 @@ function Player() { />
+ onClick={signup}> Don't have an Account? Sign Up
- {!hasRated && }
+ className="nav-toia_icon app-opensans-normal"> TOIA
+ className="nav-about_icon app-monsterrat-black "> About Us
+ className="nav-talk_icon app-monsterrat-black "> Talk To TOIA
+ className="nav-my_icon app-monsterrat-black "> My TOIA
+ className="nav-login_icon app-monsterrat-black"> {isLoggedIn ? "Logout" : "Login"}
@@ -760,8 +671,7 @@ function Player() { + classNames="fade"> + onClick={micStatusChange}> ) : null} @@ -841,7 +747,6 @@ function Player() { setRefreshQuestionsFalse={ setRefreshQuestionsFalse } - hasRated={hasRated} notificationManager={NotificationManager} /> @@ -850,8 +755,7 @@ function Player() { className="ui linkedin button submit-button" onClick={e => { submitResponse(e, "SEARCH"); - }} - > + }}> ASK diff --git a/interface/src/pages/Recorder.css b/interface/src/pages/Recorder.css index 3478c22f..9749456c 100644 --- a/interface/src/pages/Recorder.css +++ b/interface/src/pages/Recorder.css @@ -649,3 +649,7 @@ video { margin-top: 8px; border-radius: 5px; } + +.cursor-pointer { + cursor: pointer; +} diff --git a/interface/src/pages/Recorder.js b/interface/src/pages/Recorder.js index ecf35510..19e49c01 100644 --- a/interface/src/pages/Recorder.js +++ b/interface/src/pages/Recorder.js @@ -1,30 +1,26 @@ -import React, { useState, useEffect, useRef } from "react"; -import Webcam from "react-webcam"; import axios from "axios"; -import SpeechRecognition, { - useSpeechRecognition, -} from "react-speech-recognition"; -import history from "../services/history"; -import { Button, Image, Modal, Popup, TextArea, Icon } from "semantic-ui-react"; -import { Multiselect } from "multiselect-react-dropdown"; import { default as EditCreateMultiSelect } from "editable-creatable-multiselect"; +import getBlobDuration from "get-blob-duration"; +import { Multiselect } from "multiselect-react-dropdown"; +import React, { useEffect, useRef, useState } from "react"; +import { NotificationManager } from "react-notifications"; +import NotificationContainer from "react-notifications/lib/NotificationContainer"; +import { useSpeechRecognition } from "react-speech-recognition"; import Switch from "react-switch"; -import { - RecordAVideoCard, - OnBoardingQCard, - SuggestedQCard, - SuggestedQCardNoAction, -} from "./AvatarGardenPage"; +import Webcam from "react-webcam"; +import { Button, Icon, Image, Modal, Popup, TextArea } from "semantic-ui-react"; +import videoTypesJSON from "../configs/VideoTypes.json"; import CheckMarkIcon from "../icons/check-mark-success1.webp"; import RecordButton from "../icons/record1.png"; import RecordingGif from "../icons/recording51.gif"; -import videoTypesJSON from "../configs/VideoTypes.json"; -import io from "socket.io-client"; +import history from "../services/history"; import speechToTextUtils from "../transcription_utils"; import Tracker from "../utils/tracker"; -import NotificationContainer from "react-notifications/lib/NotificationContainer"; -import { NotificationManager } from "react-notifications"; -import getBlobDuration from "get-blob-duration"; +import { + OnBoardingQCard, + RecordAVideoCard, + SuggestedQCardNoAction, +} from "./AvatarGardenPage"; const videoConstraints = { width: 720, @@ -57,8 +53,7 @@ function ModalQSuggestion(props) { open={props.active} onClose={ModalOnClose} onOpen={ModalOnOpen} - trigger={} - > + trigger={}> Successful! Your TOIA has been saved. @@ -123,6 +118,7 @@ function Recorder() { command: "*", }); + const uploadElementRef = useRef(null); const webcamRef = useRef(null); const mediaRecorderRef = useRef(null); const [capturing, setCapturing] = useState(false); @@ -630,6 +626,7 @@ function Recorder() { save_as_new = false, old_video_id = "", old_video_type = "", + from_upload = false, ) => { return new Promise((resolve, reject) => { let endTimestamp = +new Date(); @@ -647,9 +644,11 @@ function Recorder() { form.append("streams", JSON.stringify(listStreams)); form.append("video_duration", videoDuration.toString()); - form.append("start_time", recordStartTimestamp); - form.append("end_time", endTimestamp); - setRecordEndTimestamp(endTimestamp); + if (!from_upload){ + form.append("start_time", recordStartTimestamp); + form.append("end_time", endTimestamp); + setRecordEndTimestamp(endTimestamp); + } if (is_editing) { form.append("is_editing", true); @@ -947,8 +946,7 @@ function Recorder() { : backgroundDefaultColor, }} onClick={onClickHandler} - video-type={props.type} - > + video-type={props.type}> {props.buttonText}

} @@ -979,6 +977,20 @@ function Recorder() { return jsx; }; + const handleVideoUpload = e => { + const file = e.target.files[0]; + if (file) { + setRecordedVideo(file); + setAnswerProvided(""); + setViewingRecordedVideo(true); + } + }; + + const onUploadClick = () => { + // simulate click on hidden file input element + uploadElementRef.current.click(); + }; + return (
+ className="nav-toia_icon app-opensans-normal"> TOIA
+ className="nav-about_icon app-monsterrat-black"> About Us
+ className="nav-talk_icon app-monsterrat-black "> Talk To TOIA
+ className="nav-my_icon app-monsterrat-black "> My TOIA
+ className="nav-login_icon app-monsterrat-black "> Logout
@@ -1043,8 +1050,7 @@ function Recorder() { backgroundColor: isPrivate ? backgroundDefaultColor : backgroundActiveColor, - }} - > + }}> Public 0 && !isRecording } - content={"Record a video to proceed"} + content={"Record/Upload a video to proceed"} trigger={
+ className="right floated recorder-next-btn-wrapper"> @@ -1156,12 +1160,27 @@ function Recorder() { icon labelPosition="left" className="right floated" - onClick={handleStartCaptureClick} - > + onClick={handleStartCaptureClick}> Record Again )} + +
+ + + Upload +
+ data-tooltip="Stop Recording"> {/* */} {/* */} {/* */} @@ -1207,8 +1225,7 @@ function Recorder() { )} @@ -1254,8 +1270,7 @@ function Recorder() { disabled={ waitingServerResponse || !videoDuration - } - > + }> Save As New } @@ -1268,8 +1283,7 @@ function Recorder() { waitingServerResponse || !videoDuration } - positive - > + positive> Update @@ -1282,8 +1296,7 @@ function Recorder() { waitingServerResponse || !videoDuration } - onClick={handleDownload} - > + onClick={handleDownload}> Save Video )} @@ -1295,8 +1308,7 @@ function Recorder() { onClick={() => setViewingRecordedVideo(false) } - disabled={waitingServerResponse} - > + disabled={waitingServerResponse}> Record Again @@ -1323,8 +1335,7 @@ function Recorder() { videoPlaybackRef.current.currentTime = 0; } }} - ref={videoPlaybackRef} - > + ref={videoPlaybackRef}> div { + padding: 10.2px; + flex: 1; +} + +/* creating card for shhh */ + + +.shhh-page-setup { + position: relative; + width: 80vw; + height: 80vh; +} + +.shhh-carousel-card::-webkit-scrollbar { + display: none; + width: 0; + } + +.shhh-carousel-card { + display: flex; + height:100%; + overflow-x: scroll; +} + +.shhh-carousel-card > div { + margin:20px; +} + +.Birth, .Sex, .Death{ + background-color: rgba(255, 255, 255, 0.298); + border-radius: 20px; + cursor: pointer; + display: flex; + flex-direction: column; + gap: 24px; + transition: all 0.2s ease; + width: 100%; +} + + +.Birth-img, .Sex-img, .Death-img { + height: 65%; +} + + .Sex-img{ + width: 75%; +} + +.Birth-img { + width: 75%; + margin-left: auto; +} + + +.valign-text-middle { + display: flex; + flex-direction: column; + justify-content: center; +} + +.subhead { + align-items: flex-start; + align-self: stretch; + display: flex; + flex-direction: column; + gap: 10px; + height: 55px; +} + +.Birth:hover, .Sex:hover, .Death:hover { + transform: scale(1.1); +} + + +.headline-subhead { + align-items: flex-start; + display: flex; + flex-direction: column; + padding: 1.5vw; + position: relative; + width: 100%; +} + +.headline { + align-items: flex-start; + align-self: stretch; + display: flex; + width: 100%; +} + +.heading { + flex: 1; + font-size: 1.5vw; + font-weight: 600; + padding: 0, 1vw; + white-space: nowrap; + } + +.Birth-title { + color: #de8163; +} + +.Sex-title { + color: #9489dd; + } + +.Death-title { + color: #ccc47c; + } + +.description{ + letter-spacing: 0; + line-height:1.3; + margin-top: 1vw; + width: 100%; + color: white; + font-size: 1vw; + font-weight: 400; +} + +.elephant { + position: absolute; + opacity:0.2; + width: 50vw; + top: 50%; + left: 50vw; + transform: translate(-50%, -50%); + } + +/* ShhhPlayer*/ +.shhh-player { + padding: 0 5vw; + background-color: #302D2D; + height: 100%; + margin: 0px; + display: flex; + align-items: center; + position: relative; + width: 100vw; +} + + +.shhh-player-cards-wrapper { + flex: 1 1 auto; + display: flex; + flex-wrap: wrap; + position: absolute; + right: 0%; + padding: 0.1vw; + width: 25%; + max-height: 70%; + overflow-y: scroll; + } + +.shhh-player-cards-wrapper::-webkit-scrollbar { +display: none; +} + + +.shhh-player-cards-wrapper > div { + margin-bottom: 9%; + +} + +.ask_box .ui.cards { + margin: 0; +} + +.shhh-player-cards-wrapper .ui.card { + flex-direction: row !important; + width: 22vw; +} + +.shhh-player-cards-wrapper .ui.buttons{ + flex-direction: column !important; +} +.shhh-player-cards-wrapper .button { + width: 100% !important; +} +.ask_box .ui.buttons .button:first-child { + border-radius: 0.28571429rem; + margin-bottom: 0.5vw; +} + +.ask_box .ui.buttons .button:last-child { + border-radius: 0.28571429rem; + +} + +.shhh-player-cards-wrapper .ui.card > .content > .description { + + color: #b9d2e9 !important; +} + +.shhh-player-group{ + position: relative; + width:100vw; + height:100vh; +} +.ask_box{ + position: absolute; + display: flex; /* set the container to use flexbox */ + flex-direction: column; + top:20%; + height: 80%; + width: 90vw; + right:0%; +} + +.shhh-submit-button { + position:absolute; + top: 36.5vw; + right:0%; + width: 4vw; + height: 4vw; + font-size: 1.5rem; +} + +.shhh-player-vid { + background-color: transparent; + top: 20%; + left: 0%; + position: absolute; + width: 70%; + border-radius: 5px; +} + +.shhh-mute-button { + position: fixed; + margin: 0 !important; + top: 49vw; + width: 10%; + height: 4vw; + font-size: 1vw; + background-color: #614CB8 !important; + z-index: 999999; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + } + + .shhh-mute-button i { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + } + + .shhh-transcript { + background-color: transparent; + border: 0; + border-bottom: 1px solid #707070; + border-radius: 0; + height: 4vw; + padding: 5px; + padding-left: 15px; + position: fixed; + resize: none; + text-align: left; + top: 49vw; + margin-left: 12%; + width: 51%; + color: #707070; + font-size: 1.5rem; + } + +.ask_box .css-1qqsdnr-MuiAutocomplete-root .MuiOutlinedInput-root { + height: 4vw !important; +} + +.ask_box .MuiInputBase-input{ + height:4vw !important; + padding:0; + padding-left: 1vw; +} + +.MuiInputBase-root.MuiOutlinedInput-root.MuiInputBase-colorPrimary.MuiInputBase-fullWidth.MuiInputBase-formControl.MuiAutocomplete-inputRoot.css-md26zr-MuiInputBase-root-MuiOutlinedInput-root{ + height: 4vw; + border-color: transparent; +} +.AskMe { + width: 20vw; + position: absolute; + right:3vw; + line-height: 2vw; + color: white; + font-size: 1vw; + +} + +.icons { + top: 55%; + right: 7vw; + width: 20vw; /* adjust the width to fit the 3x3 grid */ + position: absolute; + display: grid; /* change display to grid */ + grid-template-columns: repeat(4, 1fr); /* create three columns with equal width */ + grid-gap: 2vw; /* add gap between icons */ + justify-content: center; /* center the icons horizontally */ + } + + .icons img { + border-radius: 10%; + height: 100%; + width: auto; + filter: grayscale(0%); + z-index: 7; + transition: filter 0.3s ease-in-out; + } + + + .icons img.selected { + opacity: 1; + } + + .icon-container { + position: relative; + color: white; + display: flex; + flex-direction: column; + align-items: center; + height: 4vw; /* set a fixed height to match the image */ + } + +.icon-description { + position: absolute; + bottom: -6vw; + left: 50%; + transform: translateX(-50%); + width: 15vw; + background-color: black; + color: white; + padding: 10px; + opacity: 0; + transition: opacity 0.3s ease-in-out; + z-index: 3; /* Increase the z-index value */ +} + + .icon-container.muted .icon-name { + color: #999; /* Change to the desired muted color */ +} + .icon-container:hover .icon-description { + opacity: 1; + width: 15vw; + z-index:999999; + } + + .icon-container.muted img { + /* Apply styling to muted icons */ + opacity: 0.5; + } + + .icons.toggle-on .icon-container:not(.selected) { + /* Apply styling to muted icons when toggle is on */ + opacity: 0.5; + } + + .icons.toggle-off .icon-container { + /* Apply styling to unmuted icons when toggle is off */ + opacity: 1; + } + + .shhh-skip-end-button { + position: absolute; + top: 65%; + left: 50%; + width: 15%; + height: 7%; + font-size: 1.8rem; + z-index: 9; +} + +.back-to-shhh { + position: absolute; + top: 10%; + left: 5%; + z-index: 999; +} + +.back-to-shhh i { + font-size: 2rem; /* adjust the size as needed */ + color: white; /* set the color to white */ + transition: transform 0.3s ease-in-out; /* add transition for hover effect */ +} + +.back-to-shhh i:hover { + transform: scale(1.2); /* make the icon slightly bigger on hover */ +} \ No newline at end of file diff --git a/interface/src/pages/ShhhPage.js b/interface/src/pages/ShhhPage.js new file mode 100644 index 00000000..7be05467 --- /dev/null +++ b/interface/src/pages/ShhhPage.js @@ -0,0 +1,211 @@ +//Landing page for Elephant in the room +import React, { useState, useEffect } from "react"; +import Fuse from "fuse.js"; +import sampleVideo from "../icons/sample-video.svg"; +import submitButton from "../icons/submit-button.svg"; +import history from "../services/history"; +import { Modal } from "semantic-ui-react"; +import axios from "axios"; +import Tracker from "../utils/tracker"; +import elephant from "../images/elephant.png"; +import './ShhhPage.css'; + +//Description for each card +const BirthText = "Ask moms about their childbirth experiences that you're too afraid to ask, from labor horror stories to body transformations."; +const DeathText = "Join in a heartfelt conversation about death with elderly individuals and people with chronic conditions. Are you scared of death?"; +const SexText = "Don't be shy! Join us to have open, juicy, and playful conversations about sex that you are curious but don't dare to ask in real-life. We welcome your curiosity!"; + + +function ShhhPage() { + + const [open2, dispatch2] = useState(false); // this is to open the view pop up + + function openModal2(e) { + dispatch2(true); + e.preventDefault(); + } + + const [open3, dispatch3] = useState(false); // this is to open the view pop up + + function openModal3(e) { + dispatch3(true); + e.preventDefault(); + } + + const [toiaName, setName] = useState(null); + const [toiaLanguage, setLanguage] = useState(null); + const [toiaID, setTOIAid] = useState(null); + const [isLoggedIn, setLoginState] = useState(false); + const [allData, setAllData] = useState([]); + const [searchData, setSearchData] = useState([]); + + const [interactionLanguage, setInteractionLanguage] = useState(null); + + useEffect(() => { + if (history.location.state != undefined) { + setLoginState(true); + setName(history.location.state.toiaName); + setLanguage(history.location.state.toiaLanguage); + setTOIAid(history.location.state.toiaID); + } + + console.log("Trying my best here!", history.location.state?.toiaID); + + axios.get(`/api/getAllStreams`).then(res => { + let user_id = history.location.state?.toiaID; + axios + .get(`/api/permission/streams?user_id=${user_id}`) + .then(permission_res => { + let filtered_streams = res.data.filter(item => { + return permission_res.data.includes(item.id_stream); + }); + setAllData(filtered_streams); + setSearchData(filtered_streams); + }); + }); + + // Track + new Tracker().startTracking(history.location.state); + }, []); + + function goToPlayer(element) { + if (isLoggedIn) { + history.push({ + pathname: "/shhhplayer", + state: { + toiaName, + toiaLanguage, + toiaID, + toiaToTalk: element.id, + toiaFirstNameToTalk: element.first_name, + toiaLastNameToTalk: element.last_name, + streamToTalk: element.id_stream, + streamNameToTalk: element.name + " stream", + }, + }); + } else { + history.push({ + pathname: "/shhhplayer", + state: { + toiaToTalk: element.id, + toiaFirstNameToTalk: element.first_name, + toiaLastNameToTalk: element.last_name, + streamToTalk: element.id_stream, + streamNameToTalk: element.name + " stream", + }, + }); + } + } + //Display stream cards under the name of "Birth, Sex and Death" + const renderStream = (card, index) => { + console.log(card); + console.log('cardname' + card.name); + const orderedCards = ["Birth", "Sex", "Death"]; + if (orderedCards.includes(card.name)) { + //cards for streams + return ( +
+
{ + goToPlayer(card); + }}> + NONE +
+
+
+ {card.name} +
+
+
+ {(() => { + switch (card.name) { + case "Birth": + return BirthText; + case "Death": + return DeathText; + case "Sex": + return SexText; + default: + return ""; + } + })()} +
+
+
+
+ ); + } + }; + + const inlineStyle = { + modal: { + height: '560px', + width: '600px', + } + }; + + const inlineStyleSetting = { + modal: { + height: '70vh', + width: '50vw', + } + }; + + return ( +
+ ELEPHANT IN THE ROOM +

Elephant In the Room

+ dispatch3({ type: "close" })} + > + +

+ All Stream{" "} +

+

+ Here is the following information about your stream +

+
+ +
+ Name:{" "} +
+

+ Stream Name{" "} +

+ +
+ Language:{" "} +
+

+ Stream Language{" "} +

+ +
+ Bio:{" "} +
+ +

+ {" "} + Stream Bio +

+
+
+
+
+ {searchData.map(renderStream)} +
+
+
+ ); +} + +export default ShhhPage; diff --git a/interface/src/pages/ShhhPlayer.js b/interface/src/pages/ShhhPlayer.js new file mode 100644 index 00000000..07392600 --- /dev/null +++ b/interface/src/pages/ShhhPlayer.js @@ -0,0 +1,901 @@ +//Elephant in the Room Interaction Page +import RecordVoiceOverRoundedIcon from "@mui/icons-material/RecordVoiceOverRounded"; +import VoiceOverOffRoundedIcon from "@mui/icons-material/VoiceOverOffRounded"; +import axios from "axios"; +import React, { useEffect, useState } from "react"; +import { NotificationManager } from "react-notifications"; +import NotificationContainer from "react-notifications/lib/NotificationContainer"; +import { CSSTransition, TransitionGroup } from "react-transition-group"; +import { Modal } from "semantic-ui-react"; +import submitButton from "../icons/submit-button.svg"; +import history from "../services/history"; +import SuggestiveSearch from "../suggestiveSearch/shhhsuggestiveSearch.js"; +import speechToTextUtils from "../transcription_utils"; +import Tracker from "../utils/tracker"; +import ShhhVideoPlaybackPlayer from "./sub-components/shhh-Videoplayback.Player"; +import './ShhhPage.css'; +//Characters in each stream +//Death +import james from "../images/death/james.png"; +import rina from "../images/death/rina.png"; +import sally from "../images/death/sally.png"; +import brian from "../images/death/brian.png"; +import jj from "../images/death/jj.png"; +import ian from "../images/death/ian.png"; +import layla from "../images/death/leila.png"; +import norman from "../images/death/norman.png"; +//Sex +import lucy from "../images/sex/lucy.png"; +import lauren from "../images/sex/lauren.png"; +import klarissa from "../images/sex/klarissa.png"; +import ellis from "../images/sex/ellis.png"; +import kenny from "../images/sex/kenny.png"; +import kendel from "../images/sex/kendel.png"; +import chris from "../images/sex/chris.png"; +import zaldy from "../images/sex/zaldy.png"; +//Birth +import krista from "../images/birth/krista.png"; +import ashley from "../images/birth/ashley.png"; +import alison from "../images/birth/alison.png"; +import kieona from "../images/birth/kieona.png"; +import kaylee from "../images/birth/kaylee.png"; +import christian from "../images/birth/christian.png"; +import stephanie from "../images/birth/stephanie.png"; + +const death_icons = [brian, jj, layla, james, rina, sally, ian, norman]; +const sex_icons = [lucy, lauren, klarissa, ellis, kenny, kendel, chris, zaldy]; +const birth_icons = [krista, ashley, alison, kieona, kaylee, christian, stephanie]; +const death_names = ['Brian', 'JJ', 'Layla', 'James', 'Rina', 'Sally', 'Ian', 'Norman']; +const sex_names = ['Lucy', 'Lauren', 'Klarissa', 'Ellis', 'Kenny', 'Kendel', 'Chris', 'Zaldy']; +const birth_names = ['Krista', 'Ashley', 'Alison', 'Kieona', 'Kaylee', 'Christian', 'Stephanie']; +const death_descriptions = ['[Brian] A man with Parkinsons and its symptoms that can lead to earlier death', '[JJ] A 34-year-old man has been diagnosed with incurable colon cancer', '[Layla] A woman with end-stage renal disease', '[James] A Singaporean Man talks about life and superstition', '[Rina] A humorous Singaporean woman who has no regrets in life', '[Sally] A Singaporean woman who speaks about death in Mandarin', '[Ian] Singaporean Man', '[Norman] A Singaporean man who learned how to accept death']; +const sex_descriptions = ['[Lucy] A young woman who grew up in a culture of taboo around sex', '[Lauren] A sexual confidence coach who encourages open and non-judgmental conversations about sex', '[Klarissa] A bubbly young woman who talks about her first sexual experience and the things she wished she knew beforehand', '[Ellis] A gay man discusses his frustration with categorizing people and setting expectations in sexual encounters', '[Kenny] A gay man shares his insights on gay pornography and the importance of setting boundaries', '[Kendel] A gay man who gives a candid advice for first-time sex', '[Chris] A straight man who shares his past sexual insecurities and anxieties', '[Zaldy] A straight man who shares his definition of consent and shares his sexual insecurities, pressures, and more']; +const birth_descriptions = ['[Krista] A mother with one child delivered vaginally in a hospital setting and had an epidural.', '[Ashley]A mother of two children, both born vaginally.', + '[Alison] A mother of two daughters delivered via two scheduled c-sections', '[Kieona] A mother of one daughter had a regular vaginal birth.', '[Kaylee] A mother who shares her candid pregnancy experiences', '[Christian] A mother who gave a birth through c-section and shares her high-blood pressure issues during pregnancy', '[Stephanie] A mother who underwent an emergency C-section and shares her body-transformation stories during pregnancy.']; +//Questions listed to help users ideate type of questions they can ask +const death_questions = [ + "Are you afraid of death?", + "Who is not invited to your funeral?", + "How does dying feel like?", + "Who would be happy that you are gone?", + "What would you miss the most?", + "Have you tried giving up?" +]; +const sex_questions = [ + "What does penetration feel like?", + "Do you get emotionally attached?", + "Top or bottom?", + "What should I know about gay sex?", + "Any advice for someone's first time?", + "How often do you think about sex?", + "Is period sex okay?" +]; + +const birth_questions = [ + "What was labor like?", + "Did you use epidural (spinal anesthesia)?", + "Did you see your baby come out?", + "How painful was labor?", + "What's your most embarrassing story?", + "Does your body change after giving birth?", + "What’s the hardest part of pregnancy?", + "Did you plan pregnancy?" +]; + +function ShhhPlayer() { + function exampleReducer(state, action) { + switch (action.type) { + case "close": + return { open: false }; + case "open": + return { open: true }; + } + } + const [toiaName, setName] = React.useState(null); + const [toiaLanguage, setLanguage] = React.useState(null); + const [toiaID, setTOIAid] = React.useState(null); + const [isLoggedIn, setLoginState] = useState(false); + + const [toiaFirstNameToTalk, setTOIAFirstNameToTalk] = useState(null); + const [toiaLastNameToTalk, setTOIALastNameToTalk] = useState(null); + const [streamNameToTalk, setStreamNameToTalk] = useState(null); + const [fillerPlaying, setFillerPlaying] = useState(true); + + const [answeredQuestions, setAnsweredQuestions] = useState([]); + let allSuggestedQuestions = []; + + const [videoProperties, setVideoProperties] = useState(null); + const [transcribedAudio, setTranscribedAudio] = useState(""); + + + const [icons, setIcons] = useState([]); + const [iconNames, setIconNames] = useState([]); + const [iconDescriptions, setIconDescriptions] = useState([]); + + const textInput = React.useRef(""); + const question = React.useRef(""); + const isFillerPlaying = React.useRef("true"); + const allQuestions = React.useRef([]); + const shouldRefreshQuestions = React.useRef(false); // flag to indicate that the SuggestionCard module needs to refresh questions + + const interimTextInput = React.useRef(""); + + var input1, input2; + const [micMute, setMicMute] = useState(true); + const [micString, setMicString] = useState("ASK BY VOICE"); + const interacting = React.useRef(false); // Ref for indicating whether the user is currently interacting or not + const [textInputValue, setTextInputValue] = React.useState(""); // Stores the value of the text input + const [dataIsFinal, setDataIsFinal] = React.useState(null) // Differentiates space key up with and without data + + useEffect(() => { + // Login check. Note: This is very insecure and naive approach. Replace once a proper authentication system has been adopted. + if ( + history.location.state === undefined || + history.location.state.toiaName === undefined || + history.location.state.toiaLanguage === undefined || + history.location.state.toiaID === undefined + ) { + alert("You must be logged in to access this page"); + history.push({ + pathname: "/", + }); + } + setName(history.location.state.toiaName); + setLanguage(history.location.state.toiaLanguage); + setTOIAid(history.location.state.toiaID); + + if (history.location.state.toiaID != undefined) { + setLoginState(true); + setTOIAid(history.location.state.toiaID); + setName(history.location.state.toiaName); + setLanguage(history.location.state.toiaLanguage); + } + + setTOIAFirstNameToTalk(history.location.state.toiaFirstNameToTalk); + setTOIALastNameToTalk(history.location.state.toiaLastNameToTalk); + setStreamNameToTalk(history.location.state.streamNameToTalk); + canAccessStream(); + + fetchAnsweredQuestions(); + fetchAllStreamQuestions(); + + fetchFiller(); + // Tracker + new Tracker().startTracking(history.location.state); + }, []); + + + useEffect(() => { + // set the icon and icon name arrays based on streamNameToTalk + if ({ streamNameToTalk }.streamNameToTalk === "Death stream") { + setIcons(death_icons); + setIconNames(death_names); + setIconDescriptions(death_descriptions); + } else if ({ streamNameToTalk }.streamNameToTalk === "Sex stream") { + setIcons(sex_icons); + setIconNames(sex_names); + setIconDescriptions(sex_descriptions); + } else if ({ streamNameToTalk }.streamNameToTalk === "Birth stream") { + setIcons(birth_icons); + setIconNames(birth_names) + setIconDescriptions(birth_descriptions); + } + }, [streamNameToTalk]); + + //ask question on enter + useEffect(() => { + const listener = event => { + if (event.code === "Enter" || event.code === "NumpadEnter") { + event.preventDefault(); + + submitResponse(event, "SEARCH"); + } + }; + document.addEventListener("keydown", listener); + return () => { + document.removeEventListener("keydown", listener); + }; + }, []); + + //press space bar to unmute + const [isSpaceKeyPressed, setIsSpaceKeyPressed] = React.useState(false); + const isSpaceKey = React.useRef("false") + const firstRender = React.useRef(false) + + //identify whether the spacebar is up or down (recording or not) + useEffect(() => { + // listener for spacebar down (not for while typing) + const listenDown = event => { + if (textInput.current == "" && event.code === "Space" && isSpaceKey.current === "false") { + setIsSpaceKeyPressed(true) + isSpaceKey.current = "true" + setMicMute(false) + console.log("key down"); + } + }; + // listener for spacebar up + const listenUp = event => { + if (textInput.current == "" && event.code === "Space" && isSpaceKey.current === "true") { + setIsSpaceKeyPressed(false) + isSpaceKey.current = "false" + setMicMute(true) + console.log("key up"); + } + }; + document.addEventListener("keydown", listenDown); + document.addEventListener("keyup", listenUp); + return () => { + document.removeEventListener("keydown", listenDown); + document.removeEventListener("keyup", listenUp); + }; + }, []); + + //identify whether it is now being recorded, or not + useEffect((e) => { + + if (firstRender.current) { + if (!micMute) { + speechToTextUtils.stopRecording(); + if (dataIsFinal === false) { + textInput.current = transcribedAudio + submitResponse(e, "SEARCH") + } + + } else { + interacting.current = true; + speechToTextUtils.initRecording(handleDataReceived, (error) => { + console.error("Error when transcribing", error); + // No further action needed, as stream already closes itself on error + }); + } + } else { + firstRender.current = true + } + + + }, [isSpaceKeyPressed]) + + + + const [state, dispatch] = React.useReducer(exampleReducer, { open: false }); + const { open } = state; + + function openModal(e) { + dispatch({ type: "open" }); + e.preventDefault(); + } + + function myChangeHandler(event) { + event.preventDefault(); + var name = event.target.name; + + switch (name) { + case "email": + input1 = event.target.value; + break; + case "pass": + input2 = event.target.value; + break; + } + } + + function canAccessStream() { + let toiaID = history.location.state.toiaID; + let streamID = history.location.state.streamToTalk; + + const options = { + method: "POST", + url: "/api/permission/stream", + headers: { "Content-Type": "application/json" }, + data: { user_id: toiaID, stream_id: streamID }, + }; + + axios + .request(options) + .then(function (response) { }) + .catch(function (error) { + alert("You do not have permission to access this page"); + library(); + }); + } + + // handling data recieved from server + function handleDataReceived(data) { + // setting the transcribedAudio + if (data) { + setTranscribedAudio(data.alternatives[0].transcript); + setDataIsFinal(false) + if (data.isFinal) { + question.current = data.alternatives[0].transcript; + setDataIsFinal(true) + fetchData("VOICE"); + } + } else { + console.log("no data received from server"); + } + } + + // Sets the value of 'allQuestions' array + // to be the string list of all questions of the stream + function fetchAllStreamQuestions() { + axios + .get( + `/api/questions/answered_filtered/${history.location.state.toiaToTalk}/${history.location.state.streamToTalk}`, + ) + .then(res => { + allQuestions.current = res.data || []; + }); + } + // function to fetch answered smart questions from the database + // question: the last question asked by the user + // answer: the answer given by the avatar + function fetchAnsweredQuestions(question = "", answer = "") { + axios + .post("/api/getSmartQuestions", { + params: { + toiaIDToTalk: history.location.state.toiaToTalk, + toiaFirstNameToTalk: + history.location.state.toiaFirstNameToTalk, + avatar_id: history.location.state.toiaToTalk, + stream_id: history.location.state.streamToTalk, + latest_question: question, + latest_answer: answer, + }, + }) + .then(res => { + // res.data contains the smart questions generated + // If res.data is not an array, then there is an error, so stop. + if (res.data.constructor !== Array) { + return; + } + + res.data.forEach(q => { + // If the question is not an empty string AND has not been previously asked AND is not in the current list of possible questions + // then add it to the list of possible suggestions + if ( + q.question != "" && + !allSuggestedQuestions.includes(q) + ) { + answeredQuestions.push(q); + allSuggestedQuestions.push(q); + } + }); + + shouldRefreshQuestions.current = true; + setAnsweredQuestions([...answeredQuestions]); + }); + } + + + function fetchData(mode = "UNKNOWN") { + const oldQuestion = question.current; + if (question.current == null || question.current == "") { + setFillerPlaying(true); + fetchFiller(); + } else { + axios + .post(`/api/player`, { + params: { + toiaIDToTalk: history.location.state.toiaToTalk, + + toiaFirstNameToTalk: + history.location.state.toiaFirstNameToTalk, + question, + streamIdToTalk: history.location.state.streamToTalk, + record_log: "true", + ...(history.location.state.toiaID && { + interactor_id: history.location.state.toiaID, + }), + mode: mode, + }, + }) + .then(res => { + if (res.data === "error") { + setFillerPlaying(true); + } else { + setFillerPlaying(true); + + isFillerPlaying.current = "false"; + fetchAnsweredQuestions(oldQuestion, res.data.answer); + setVideoProperties({ + key: res.data.url + new Date(), // add timestamp to force video transition animation when the key hasn't changed + onEnded: () => { + fetchFiller(); + }, + source: res.data.url, + fetchFiller: fetchFiller, + muted: false, + filler: false, + duration_seconds: res.data.duration_seconds || null, + question: question.current, + video_id: res.data.video_id, + }); + + setTranscribedAudio(""); + } + }) + .catch(e => { + console.error(e); + }); + } + } + + function endTranscription() { + speechToTextUtils.stopRecording(); + } + + //changeing the status of the mic button + const micStatusChange = (e) => { + if (!micMute) { + speechToTextUtils.stopRecording(); + if (dataIsFinal === false) { + textInput.current = transcribedAudio + submitResponse(e, "SEARCH") + } + + } else { + interacting.current = true; + speechToTextUtils.initRecording(handleDataReceived, (error) => { + console.error("Error when transcribing", error); + // No further action needed, as stream already closes itself on error + }); + } + + setMicMute(!micMute); + setMicString(micMute ? "STOP ASK BY VOICE" : "ASK BY VOICE"); + interacting.current = micMute ? true : false; + }; + + function fetchFiller() { + isFillerPlaying.current = "true"; + if (fillerPlaying) { + axios + .post(`/api/fillerVideo`, { + params: { + toiaIDToTalk: history.location.state.toiaToTalk, + streamIdToTalk: history.location.state.streamToTalk, + toiaFirstNameToTalk: + history.location.state.toiaFirstNameToTalk, + record_log: "true", + ...(history.location.state.toiaID && { + interactor_id: history.location.state.toiaID, + }), + }, + }) + .then(async res => { + setVideoProperties({ + key: res.data + new Date(), // add timestamp to force video transition animation when the key hasn't changed + onEnded: fetchFiller, + source: res.data, + muted: true, + filler: true, + duration_seconds: null, + }); + + document.getElementById("vidmain").load(); + const playPromise = document + .getElementById("vidmain") + .play(); + if (playPromise !== undefined) { + playPromise + .then(_ => { + // Automatic playback started! + // Show playing UI. + }) + .catch(error => { + // Auto-play was prevented + // Show paused UI. + + fetchFiller(); + }); + } + }); + } + } + + function setRefreshQuestionsFalse() { + shouldRefreshQuestions.current = false; + } + + function textChange(e) { + textInput.current = `${textInput.current} ${e.target.value}`; + interimTextInput.current = textInput.current; + setTextInputValue(e.target.value); + } + + function submitResponse(e, mode = "UNKNOWN") { + question.current = textInput.current + ? textInput.current + : interimTextInput.current; + if (question.current != "") { + const oldQuestion = question.current; + axios + .post(`/api/player`, { + params: { + toiaIDToTalk: history.location.state.toiaToTalk, + toiaFirstNameToTalk: + history.location.state.toiaFirstNameToTalk, + question, + streamIdToTalk: history.location.state.streamToTalk, + record_log: "true", + ...(history.location.state.toiaID && { + interactor_id: history.location.state.toiaID, + }), + mode: mode, + }, + }) + .then(res => { + setFillerPlaying(true); + if (res.data !== "error") { + isFillerPlaying.current = "false"; + + setVideoProperties({ + key: res.data.url + new Date(), // add timestamp to force video transition animation when the key hasn't changed + onEnded: () => { + fetchFiller(); + }, + source: res.data.url, + muted: false, + filler: false, + duration_seconds: res.data.duration_seconds || null, + question: question.current, + video_id: res.data.video_id, + }); + + fetchAnsweredQuestions(oldQuestion, res.data.answer); + setTranscribedAudio(""); + question.current = ""; + } + }) + .catch(e => { + console.error(e); + }); + textInput.current = ""; + interimTextInput.current = ""; + setTextInputValue(""); + } + } + + function submitHandler(e) { + e.preventDefault(); + + let params = { + email: input1, + pwd: input2, + }; + + axios.post(`/api/login`, params).then(res => { + if (res.data == -1) { + //alert('Email not found'); + NotificationManager.error("Incorrect e-mail address."); + } else if (res.data == -2) { + NotificationManager.error("Incorrect password."); + } else { + history.push({ + pathname: "/mytoia", + state: { + toiaName: res.data.firstName, + toiaLanguage: res.data.language, + toiaID: res.data.toia_id, + }, + }); + } + }); + } + + function home() { + if (isLoggedIn) { + endTranscription(); + history.push({ + pathname: "/", + state: { + toiaName, + toiaLanguage, + toiaID, + }, + }); + } else { + history.push({ + pathname: "/", + }); + } + } + + function shhh() { + if (isLoggedIn) { + history.push({ + pathname: "/shhh", + state: { + toiaName, + toiaLanguage, + toiaID, + }, + }); + } else { + history.push({ + pathname: "/shhh", + }); + } + } + + function about() { + if (isLoggedIn) { + endTranscription(); + history.push({ + pathname: "/about", + state: { + toiaName, + toiaLanguage, + toiaID, + }, + }); + } else { + history.push({ + pathname: "/about", + }); + } + } + + function library() { + if (history.location.state.toiaID != undefined) { + endTranscription(); + history.push({ + pathname: "/library", + state: { + toiaName: history.location.state.toiaName, + toiaLanguage: history.location.state.toiaLanguage, + toiaID: history.location.state.toiaID, + }, + }); + } else { + history.push({ + pathname: "/library", + }); + } + } + + function garden(e) { + if (isLoggedIn) { + endTranscription(); + history.push({ + pathname: "/mytoia", + state: { + toiaName, + toiaLanguage, + toiaID, + }, + }); + } else { + openModal(e); + } + } + + function logout() { + //logout function needs to be implemented (wahib) + history.push({ + pathname: "/", + }); + endTranscription(); + } + + function signup() { + history.push({ + pathname: "/signup", + }); + endTranscription(); + } + + const inlineStyle = { + modal: { + height: "560px", + width: "600px", + }, + }; + + const [selectedIconIndex, setSelectedIconIndex] = useState(null); + + + const handleIconClick = (index) => { + const iconName = iconNames[index]; + if (index === selectedIconIndex) { + setSelectedIconIndex(null); + // unmute all icons + const iconContainers = document.querySelectorAll('.icon-container'); + iconContainers.forEach((container) => { + container.classList.remove('muted'); + }); + textInput.current = ""; // reset to empty string + } else { + setSelectedIconIndex(index); + // mute all icons except selected one + const iconContainers = document.querySelectorAll('.icon-container'); + iconContainers.forEach((container, i) => { + if (i === index) { + container.classList.remove('muted'); + } else { + container.classList.add('muted'); + } + }); + textInput.current = iconName; + interimTextInput.current = textInput.current; + } + }; + + + return ( +
+ dispatch({ type: "close" })}> + +

+ Welcome Back +

+

+ Enter the following information to login to your TOIA + account +

+
+ + +
+ + + +
+ Don't have an Account? Sign Up +
+
+
+
+
+
+ +
+
+
+ {icons.map((icon, index) => ( +
+ handleIconClick(index)} + /> +
{iconNames[index]}
+
{iconDescriptions[index]}
+
+ ))} +
+
+ {videoProperties && ( + + + + + + )} + {micMute ? ( + + ) : ( + + ) + } + + + {isFillerPlaying.current == "false" ? ( + + ) : null} + + +
+ +
+ {streamNameToTalk && streamNameToTalk.includes(" stream") && ( +

Hey, let's talk about {streamNameToTalk.replace(" stream", "")}

+ )} +
    + {streamNameToTalk === "Death stream" && death_questions.map((question, index) => ( +
  • {question}
  • + ))} + {streamNameToTalk === "Sex stream" && sex_questions.map((question, index) => ( +
  • {question}
  • + ))} + {streamNameToTalk === "Birth stream" && birth_questions.map((question, index) => ( +
  • {question}
  • + ))} +
+
+
+ +
{ + e.preventDefault(); + submitResponse(e, "SEARCH"); + + }}> + +
+
+ +
+ +
+ ); +} +export default ShhhPlayer; \ No newline at end of file diff --git a/interface/src/pages/sub-components/shhh-Videoplayback.Player.js b/interface/src/pages/sub-components/shhh-Videoplayback.Player.js new file mode 100644 index 00000000..57f56eb0 --- /dev/null +++ b/interface/src/pages/sub-components/shhh-Videoplayback.Player.js @@ -0,0 +1,43 @@ +import { useRef, useState, useEffect } from "react"; + +export default function ShhhVideoPlaybackPlayer(props) { + const videoRef = useRef(null); + + // let skippedLastSecond = false; + + // useEffect(() => { + // skippedLastSecond = false; + // }, [props]) + + // const onTimeUpdate = () => { + // if (!props.filler && props.duration_seconds && props.duration_seconds > 3){ + // if ((props.duration_seconds - videoRef.current.currentTime) <= 1){ + // if (!skippedLastSecond){ + // skippedLastSecond = true; + // props.onEnded(); + // console.log("Ending:", props.duration_seconds) + // } + // } + // } + // } + + return ( +
+ +
+ ); +} diff --git a/interface/src/routes/index.js b/interface/src/routes/index.js index a4bbd1ba..0323b945 100644 --- a/interface/src/routes/index.js +++ b/interface/src/routes/index.js @@ -1,20 +1,22 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; - import Home from "../pages/HomePage"; import SignUp from "../pages/SignUpPage"; import AvatarGarden from "../pages/AvatarGardenPage"; +import Shhh from "../pages/ShhhPage"; import AvatarLibrary from "../pages/AvatarLibraryPage"; import Settings from "../pages/AvatarStream"; import AboutUs from "../pages/AboutUsPage"; import Recorder from "../pages/Recorder"; import Player from "../pages/Player"; +import shhhPlayer from "../pages/ShhhPlayer"; import EditRecorder from "../pages/EditRecorderPage"; export default function Routes() { return ( + @@ -23,6 +25,7 @@ export default function Routes() { + {/* redirect user to SignIn page if route does not exist and user is not authenticated */} diff --git a/interface/src/services/languageHelper.js b/interface/src/services/languageHelper.js new file mode 100644 index 00000000..b0efd8c1 --- /dev/null +++ b/interface/src/services/languageHelper.js @@ -0,0 +1,12 @@ +// pan class="fi fi-us"> +// +// {/* SP */} +// { + // console.log("getSuggestion:"); + // console.log(questionCard.questionData && questionCard.questionData.question ? questionCard.questionData.question : "No question loaded!"); + return questionCard.questionData && questionCard.questionData.question + ? questionCard.questionData.question + : "Loading ..."; + }; + + const delayedQuestionCardChange = (questionCard, setQuestionCard) => { + return () => { + questionCard.highlighBackground = false; + if (questionCard.waiting) { + questionCard.questionData = null; + setQuestionCard({ ...questionCard }); + } + }; + }; + + const askQuestion = (questionCard, setQuestionCard) => { + // console.log("askQuestion:"); + props.askQuestion(questionCard.questionData); + questionCard.waiting = true; + questionCard.highlighBackground = true; + setTimeout( + delayedQuestionCardChange(questionCard, setQuestionCard), + 3000, + ); + setQuestionCard({ ...questionCard }); + }; + + const getNextQuestion = (questionCard, setQuestionCard) => { + // console.log("getNextQuestion:"); + if (props.questions.length > 0) { + // console.log("getNextQuestion: got question"); + questionCard.questionData = props.questions.pop(); + setQuestionCard({ ...questionCard }); + } + }; + + const refreshQuestion = (questionCard, setQuestionCard) => { + // console.log("refreshQuestion: refreshing question..."); + if ( + props.questions.length > 0 && + questionCard.waiting && + props.shouldRefreshQuestions?.current + ) { + // console.log("refreshQuestion: refreshed question..."); + questionCard.waiting = false; + questionCard.highlighBackground = false; + questionCard.questionData = props.questions.pop(); + // console.log("refreshQuestion: new question: ", questionCard.questionData.question); + setQuestionCard({ ...questionCard }); + } + }; + + const shouldStopRefreshing = () => { + for (let questionCard in cardQuestions) { + // If any card question is still waiting, even after updating the questions + // Then we still want to refresh questions where possible + if (questionCard.waiting && props.shouldRefreshQuestions?.current) { + return false; + } + } + return true; + }; + + useEffect(() => { + getNextQuestion(firstQuestion, setFirstQuestion); + getNextQuestion(secondQuestion, setSecondQuestion); + }, []); + + return props.questions ? ( + +
+ + + + {refreshQuestion(fourthQuestion, setFourthQuestion)} + {getSuggestion(fourthQuestion)} + + + +
+
+
+
+
+ +
+ + + + {refreshQuestion(thirdQuestion, setThirdQuestion)} + {getSuggestion(thirdQuestion)} + + + +
+
+
+
+
+ +
+ + + + {refreshQuestion(secondQuestion, setSecondQuestion)} + {getSuggestion(secondQuestion)} + + + +
+
+
+
+
+ +
+ + + + {refreshQuestion(firstQuestion, setFirstQuestion)} + {getSuggestion(firstQuestion)} + + + +
+
+
+
+
+ + {shouldStopRefreshing() ? props.setRefreshQuestionsFalse() : ""} +
+ ) : null; +} + diff --git a/interface/src/suggestiveSearch/shhhsuggestiveSearch.js b/interface/src/suggestiveSearch/shhhsuggestiveSearch.js new file mode 100644 index 00000000..45db84b5 --- /dev/null +++ b/interface/src/suggestiveSearch/shhhsuggestiveSearch.js @@ -0,0 +1,41 @@ +import React from "react"; +import TextField from "@mui/material/TextField"; +import '../pages/ShhhPage.css'; + + +export default function FreeSoloCreateOption(props) { + return ( + { + props.handleTextChange(e); + }} + /> + ); +} diff --git a/interface/src/suggestiveSearch/suggestionCards.js b/interface/src/suggestiveSearch/suggestionCards.js index 14ad6d4d..f5cdf815 100644 --- a/interface/src/suggestiveSearch/suggestionCards.js +++ b/interface/src/suggestiveSearch/suggestionCards.js @@ -1,7 +1,7 @@ // import * as React from "react"; import React, { useEffect, useState } from "react"; import { Button, Card } from "semantic-ui-react"; -import "../pages/Player.css"; +// import "../pages/Player.css"; export default function SuggestionCards(props) { // keeping track of position of each of the five cards @@ -64,15 +64,6 @@ export default function SuggestionCards(props) { }; const askQuestion = (questionCard, setQuestionCard) => { - if (!props.hasRated) { - props.notificationManager.warning( - "Please provide a rating", - "", - 3000, - ); - return; - } - // console.log("askQuestion:"); props.askQuestion(questionCard.questionData); questionCard.waiting = true; @@ -126,7 +117,7 @@ export default function SuggestionCards(props) { }, []); return props.questions ? ( - +

Some things you can ask me…

@@ -150,6 +141,7 @@ export default function SuggestionCards(props) { color="linkedin" icon="send" labelPosition="left" + onClick={() => { askQuestion( fifthQuestion, @@ -356,10 +348,10 @@ export default function SuggestionCards(props) { } /** - * - * - * - * - + * + * + * + * + */ diff --git a/interface/src/suggestiveSearch/suggestiveSearch.js b/interface/src/suggestiveSearch/suggestiveSearch.js index 7ca5331c..eb3ba612 100644 --- a/interface/src/suggestiveSearch/suggestiveSearch.js +++ b/interface/src/suggestiveSearch/suggestiveSearch.js @@ -90,7 +90,7 @@ export default function FreeSoloCreateOption(props) { top: "90%", width: "55.25%", left: "1%", - height: "7.5%", + height: "30.5%", // height: "80.25%", color: "#707070", fontSize: "1.5rem", diff --git a/server/.gitignore b/server/.gitignore index ac57606e..2d4977f3 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -7,4 +7,7 @@ package-lock.json /.idea /Accounts /speech_to_text/toia-capstone-2021-b944d1cc65aa.json -/interface/build \ No newline at end of file +/interface/build +manual_save/ +/manual_save +manual_save diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev index c63e86d5..07b5a4e9 100644 --- a/server/Dockerfile.dev +++ b/server/Dockerfile.dev @@ -21,4 +21,4 @@ RUN npm install COPY server/ . -ENTRYPOINT ["nodemon", "app.js"] \ No newline at end of file +ENTRYPOINT ["nodemon", "-L", "app.js"] \ No newline at end of file diff --git a/server/helper/user_mgmt.js b/server/helper/user_mgmt.js index 2ca082aa..f68e71ec 100644 --- a/server/helper/user_mgmt.js +++ b/server/helper/user_mgmt.js @@ -179,7 +179,7 @@ const linkStreamVideoQuestion = (streamID, videoID, quesID, type, ada_search=nul let linkQuesQuery = `INSERT INTO videos_questions_streams(id_video, id_question, id_stream, type, ada_search) VALUES(?, ?, ?, ?, ?);`; connection.query( linkQuesQuery, - [videoID, quesID, streamID, type, ada_search], + [videoID, quesID, streamID, type, JSON.stringify(ada_search)], err => { if (err) throw err; console.log( @@ -513,6 +513,7 @@ const canAccessStream = (user_id, stream_id) => { const getAccessibleStreams = user_id => { return new Promise(resolve => { let query = `(SELECT id_stream FROM stream where toia_id = ?) UNION (SELECT stream_id FROM stream_view_permission WHERE toia_id = ?);`; + // let query = `SELECT id_stream FROM stream`; connection.query(query, [user_id, user_id], (err, entries) => { if (err) throw err; let stream_ids = entries.map(item => { @@ -590,7 +591,7 @@ const getExactMatchVideo = (stream_id, question) => { const getEmbeddings = async (question, answer) => { try { const embeddings = await openai.createEmbedding({ - model: "text-search-ada-doc-001", + model: "text-embedding-ada-002", // input: "Question: " + question + "; Answer: " + answer, }); diff --git a/server/mysql/toia_db.sql b/server/mysql/toia_db.sql index 12457e29..a286844b 100644 --- a/server/mysql/toia_db.sql +++ b/server/mysql/toia_db.sql @@ -1,11 +1,11 @@ -- phpMyAdmin SQL Dump --- version 5.1.1 +-- version 5.1.3 -- https://www.phpmyadmin.net/ -- -- Host: mysql --- Generation Time: Jun 23, 2022 at 06:58 PM +-- Generation Time: Nov 27, 2022 at 06:49 AM -- Server version: 8.0.27 --- PHP Version: 7.4.27 +-- PHP Version: 8.0.16 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; START TRANSACTION; @@ -35,7 +35,9 @@ CREATE TABLE `conversations_log` ( `timestamp` bigint NOT NULL, `filler` tinyint(1) NOT NULL, `question_asked` text, - `video_played` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL + `video_played` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `ada_similarity_score` float DEFAULT NULL, + `mode` varchar(50) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- -------------------------------------------------------- @@ -164,7 +166,7 @@ CREATE TABLE `videos_questions_streams` ( `id_question` int NOT NULL, `id_stream` int NOT NULL, `type` enum('filler','greeting','answer','exit','no-answer','y/n-answer') NOT NULL, - `ada_search` text NOT NULL + `ada_search` text CHARACTER SET utf8 COLLATE utf8_general_ci ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; -- diff --git a/server/package.json b/server/package.json index a137475b..00179131 100644 --- a/server/package.json +++ b/server/package.json @@ -45,4 +45,4 @@ "prettier": "2.7.1", "supertest": "^6.2.1" } -} +} \ No newline at end of file diff --git a/server/q_api/test/test.py b/server/q_api/test/test.py index 389d9bc2..a8a14a76 100644 --- a/server/q_api/test/test.py +++ b/server/q_api/test/test.py @@ -6,7 +6,7 @@ openai.api_key = "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" def printEmbedding(query): - embedding = get_embedding(query, engine='text-search-ada-query-001') + embedding = get_embedding(query, engine='text-embedding-ada-002') print("====================\nQuery:", query, "\n Embedding:",embedding ,"\n====================") print("Starting test...", flush=True) diff --git a/server/q_api/utils.py b/server/q_api/utils.py index 638d1b28..05535ee0 100644 --- a/server/q_api/utils.py +++ b/server/q_api/utils.py @@ -43,7 +43,7 @@ def getFirstNSimilar(df_avatar, query, NUM_SHORTLIST): def getFreqByCosineSimilarity(query, data): # Creating embedding for query - embedding = get_embedding(query, engine='text-search-ada-query-001') + embedding = get_embedding(query, engine='text-embedding-ada-002') # Searching query embedding through avatar's questions' embeddings using cosine similarity data['similarities'] = data.ada_search.apply(lambda x: cosine_similarity(x, embedding)) diff --git a/server/routes/index.js b/server/routes/index.js index dc6b4dd9..fc31ff4b 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -33,6 +33,7 @@ const { getAccessibleStreams, getVideoDetails, getExactMatchVideo, + getEmbeddings, } = require("../helper/user_mgmt"); const bcrypt = require("bcrypt"); const connection = require("../configs/db-connection"); @@ -1043,11 +1044,20 @@ router.post("/recorder", cors(), async (req, res) => { } for (const streamToLink of videoStreams) { + let embeddings = ""; + try { + console.log("Generating Embeddings: question: ", q.question, " answer: ", answer); + embeddings = await getEmbeddings(q.question, answer); + } catch (err) { + console.log("Failed to generate embeddings"); + console.log(err); + } await linkStreamVideoQuestion( streamToLink.id, videoID, q_id, fields.videoType[0], + embeddings, ); } diff --git a/server/toia-dm/Dockerfile b/server/toia-dm/Dockerfile index 8d287ec0..5859eede 100644 --- a/server/toia-dm/Dockerfile +++ b/server/toia-dm/Dockerfile @@ -9,6 +9,7 @@ COPY requirements.txt /app/ WORKDIR /app +RUN apt-get install libmysqlclient-dev -y RUN pip3 install -r requirements.txt # RUN python3 -m spacy download en_core_web_lg RUN apt-get install python3-mysqldb -y diff --git a/server/toia-dm/PATCH.py b/server/toia-dm/PATCH.py index 68ccaf11..b7c81bf1 100644 --- a/server/toia-dm/PATCH.py +++ b/server/toia-dm/PATCH.py @@ -1,16 +1,8 @@ -import argparse -from ast import Num -import os -import time -from tokenize import Number - import numpy as np -import openai import pandas as pd import sqlalchemy as db from sqlalchemy import Table from dotenv import dotenv_values -from openai.embeddings_utils import get_embedding from sqlalchemy.sql import text import friendlywords as fw import json @@ -160,5 +152,5 @@ def createUserVariant(user_id): with open('result.json', 'w') as fp: json.dump(info, fp) -# populateAllAdaSearch() -createVariants() +populateAllAdaSearch() +# createVariants() diff --git a/server/toia-dm/create_embeddings.py b/server/toia-dm/create_embeddings.py index 33ad9416..37bcf003 100644 --- a/server/toia-dm/create_embeddings.py +++ b/server/toia-dm/create_embeddings.py @@ -1,47 +1,33 @@ -import os from dotenv import dotenv_values -import openai -from openai.embeddings_utils import get_embedding +from openai import OpenAI import pandas as pd -import numpy as np import sqlalchemy as db from sqlalchemy.sql import text import argparse -import time - -parser = argparse.ArgumentParser(description='Pass toia_id to insert embeddings into the db.') -parser.add_argument('-t', type=int, nargs='+', - help='toia_ids for creating embeddings and insert them into the db') -args = parser.parse_args() +# Load environment variables config = dotenv_values() -# openai.organization = config['YOUR_ORG_ID'] -openai.api_key = config['OPENAI_API_KEY'] +# Set OpenAI API key +openaiClient = OpenAI(api_key=config["OPENAI_API_KEY"]) + +# Database connection URL SQL_URL = "{dbconnection}://{dbusername}:{dbpassword}@{dbhost}/{dbname}".format( dbconnection=config["DB_CONNECTION"], dbusername=config["DB_USERNAME"], dbpassword=config["DB_PASSWORD"], dbhost=config["DB_HOST"], dbname=config["DB_DATABASE"]) -# SQL_URL = "{dbconnection}://{dbusername}:{dbpassword}@{dbhost}/{dbname}".format(dbconnection=config["DB_CONNECTION"],dbusername=config["DB_USERNAME"],dbpassword=config["DB_PASSWORD"],dbhost=config["DB_HOST"],dbname=config["DB_DATABASE"]) print(SQL_URL) -print("Connecting...") - +# Create database engine ENGINE = db.create_engine(SQL_URL) METADATA = db.MetaData() VIDEOS = db.Table('video', METADATA, autoload=True, autoload_with=ENGINE) -print("Connected successfully!") - -def adaSimilarity(x): - time.sleep(1) - return get_embedding(x, engine='text-similarity-ada-001') - -def adaSearch(x): - time.sleep(1) - return get_embedding(x, engine='text-search-ada-doc-001') +def get_embedding_new(text, model="text-embedding-ada-002"): + text = text.replace("\n", " ") + return openaiClient.embeddings.create(input=text, model=model).data[0].embedding def addAdaSearch(toiaID): retrieve_statement = text(""" @@ -49,9 +35,10 @@ def addAdaSearch(toiaID): INNER JOIN videos_questions_streams vqs ON vqs.id_video = v.id_video INNER JOIN questions q ON q.id = vqs.id_question WHERE v.toia_id = :toiaID AND v.private = 0 AND vqs.type NOT IN ('filler', 'exit');""") - CONNECTION = ENGINE.connect() - result_proxy = CONNECTION.execute(retrieve_statement, toiaID=toiaID) - result_set = result_proxy.fetchall() + with ENGINE.connect() as CONNECTION: + result_proxy = CONNECTION.execute(retrieve_statement, toiaID=toiaID) + result_set = result_proxy.fetchall() + df_avatar = pd.DataFrame(result_set, columns=[ 'toia_id', @@ -61,19 +48,23 @@ def addAdaSearch(toiaID): 'question_id' ]) df_avatar['combined'] = "Question: " + df_avatar.question.str.strip() + "; Answer: " + df_avatar.answer.str.strip() - # This will take just under 2 minutes - df_avatar['ada_similarity'] = df_avatar.combined.apply(lambda x: adaSimilarity(x)) - df_avatar['ada_search'] = df_avatar.combined.apply(lambda x: adaSearch(x)) + df_avatar['ada_search'] = df_avatar.combined.apply(lambda x: get_embedding_new(x)) for videoID in df_avatar.id_video: - adaSearchVar = str(df_avatar[df_avatar['id_video']==videoID].ada_search.values[0]) + adaSearchVar = str(df_avatar[df_avatar['id_video'] == videoID].ada_search.values[0]) update_statement = text(""" UPDATE videos_questions_streams vqs SET ada_search = :adaSearchVal WHERE vqs.id_video = :videoID; """) - CONNECTION = ENGINE.connect() - CONNECTION.execute(update_statement, adaSearchVal=adaSearchVar, videoID=videoID, toiaID=toiaID) + with ENGINE.connect() as CONNECTION: + CONNECTION.execute(update_statement, adaSearchVal=adaSearchVar, videoID=videoID, toiaID=toiaID) if __name__ == "__main__": + # Parse command line arguments + parser = argparse.ArgumentParser(description='Pass toia_id to insert embeddings into the db.') + parser.add_argument('-t', type=int, nargs='+', + help='toia_ids for creating embeddings and insert them into the db') + args = parser.parse_args() + for toiaID in args.t: - addAdaSearch(toiaID) \ No newline at end of file + addAdaSearch(toiaID) diff --git a/server/toia-dm/main.py b/server/toia-dm/main.py index c458f8f5..06a2387b 100644 --- a/server/toia-dm/main.py +++ b/server/toia-dm/main.py @@ -15,7 +15,8 @@ from pydantic import BaseModel load_dotenv() -SQL_URL = "{dbconnection}://{dbusername}:{dbpassword}@{dbhost}/{dbname}".format(dbconnection=os.environ.get("DB_CONNECTION"),dbusername=os.environ.get("DB_USERNAME"),dbpassword=os.environ.get("DB_PASSWORD"),dbhost=os.environ.get("DB_HOST"),dbname=os.environ.get("DB_DATABASE")) +SQL_URL = "{dbconnection}://{dbusername}:{dbpassword}@{dbhost}/{dbname}".format(dbconnection=os.environ.get("DB_CONNECTION"), dbusername=os.environ.get( + "DB_USERNAME"), dbpassword=os.environ.get("DB_PASSWORD"), dbhost=os.environ.get("DB_HOST"), dbname=os.environ.get("DB_DATABASE")) ENGINE = db.create_engine(SQL_URL) @@ -33,53 +34,56 @@ class QuestionInput(BaseModel): avatar_id: str stream_id: str -class DMpayload(BaseModel): #I know this nesting is stupid --- correct this later in the nodejs backend when defining the payload there + +class DMpayload(BaseModel): # I know this nesting is stupid --- correct this later in the nodejs backend when defining the payload there params: QuestionInput + @app.post("/dialogue_manager") def dialogue_manager(payload: DMpayload): raw_payload = payload.params - + query = raw_payload.query avatar_id = raw_payload.avatar_id stream_id = raw_payload.stream_id - print(query) - print(avatar_id) - print(stream_id) - statement = text("""SELECT videos_questions_streams.id_stream as stream_id_stream, videos_questions_streams.type, videos_questions_streams.ada_search, questions.question, video.id_video, video.toia_id, video.idx, video.private, video.answer, video.likes, video.views FROM video INNER JOIN videos_questions_streams ON videos_questions_streams.id_video = video.id_video INNER JOIN questions ON questions.id = videos_questions_streams.id_question WHERE videos_questions_streams.id_stream = :streamID AND video.private = 0 AND videos_questions_streams.type NOT IN ('filler', 'exit');""") CONNECTION = ENGINE.connect() - result_proxy = CONNECTION.execute(statement,streamID=stream_id) + result_proxy = CONNECTION.execute(statement, streamID=stream_id) result_set = result_proxy.fetchall() df_avatar = pd.DataFrame(result_set, - columns=[ - 'stream_id_stream', - 'type', - 'ada_search', - 'question', - 'id_video', - 'toia_id', - 'idx', - 'private', - 'answer', - 'likes', - 'views', - ]) - - df_avatar['ada_search'] = df_avatar.ada_search.apply(eval).apply(np.array) #needed when np array stored as txt + columns=[ + 'stream_id_stream', + 'type', + 'ada_search', + 'question', + 'id_video', + 'toia_id', + 'idx', + 'private', + 'answer', + 'likes', + 'views', + ]) + + print("ok connected to db") + df_avatar['ada_search'] = df_avatar.ada_search.apply( + eval).apply(np.array) # needed when np array stored as txt + print("ok ada_search") # df_greetings = df_avatar[df_avatar['type'] == "greeting"] - if query is None: - return 'Please enter a query', 400 - - response = toia_answer(query, df_avatar) + # if query is None: + # return 'Please enter a query', 400 + print(query) + response = toia_answer(query, df_avatar, ENGINE, interactor_id=avatar_id) # this works only for one toia account interacting with its strams. Need to change to focus only on interactor ID + print("does it make response") + print(response) answer = response[0] id_video = response[1] @@ -94,5 +98,6 @@ def dialogue_manager(payload: DMpayload): json.dumps(result) return result + if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("DM_PORT"))) diff --git a/server/toia-dm/notebooks/dm-dev.ipynb b/server/toia-dm/notebooks/dm-dev.ipynb index 857f9d3a..24e9e5ab 100644 --- a/server/toia-dm/notebooks/dm-dev.ipynb +++ b/server/toia-dm/notebooks/dm-dev.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 1, "id": "e2ffbe54-c168-4e86-be33-d0c646533100", "metadata": {}, "outputs": [], @@ -31,13 +31,14 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 50, "id": "1353aae9-ff9d-4967-a1f1-7512dd821948", "metadata": {}, "outputs": [], "source": [ "import sqlalchemy as db\n", "from sqlalchemy.sql import text\n", + "import mysql.connector\n", "import pandas as pd\n", "import numpy as np\n", "import numpy\n", @@ -48,8 +49,12 @@ "from sklearn.feature_extraction.text import TfidfTransformer\n", "from sklearn.metrics.pairwise import cosine_similarity\n", "from nltk.stem.snowball import SnowballStemmer\n", - "import spacy\n", - "import spacy_sentence_bert" + "# import spacy\n", + "# import spacy_sentence_bert\n", + "\n", + "from dotenv import dotenv_values\n", + "\n", + "config = dotenv_values(\".env\")" ] }, { @@ -62,76 +67,65 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 51, "id": "1a13e027-2066-4fc9-be10-47d5a69bd838", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['sentence_bert']" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "NLP = spacy.load(\"en_core_web_lg\")\n", - "# NLP_BERT = spacy.load(\"en_paraphrase_distilroberta_base_v1\")\n", - "# NLP_BERT = spacy_sentence_bert.load_model('multi-qa-mpnet-base-dot-v1')\n", - "NLP_BERT = spacy.blank('en')\n", - "NLP_BERT.add_pipe('sentence_bert', config={'model_name': 'multi-qa-MiniLM-L6-cos-v1'})\n", - "NLP_BERT.pipe_names" + "# NLP = spacy.load(\"en_core_web_lg\")\n", + "# # NLP_BERT = spacy.load(\"en_paraphrase_distilroberta_base_v1\")\n", + "# # NLP_BERT = spacy_sentence_bert.load_model('multi-qa-mpnet-base-dot-v1')\n", + "# NLP_BERT = spacy.blank('en')\n", + "# NLP_BERT.add_pipe('sentence_bert', config={'model_name': 'multi-qa-MiniLM-L6-cos-v1'})\n", + "# NLP_BERT.pipe_names" ] }, { "cell_type": "markdown", "id": "052e0142-0c2e-4aa4-8d64-b55e32350d7c", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ "### get data" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 64, "id": "18ba4c61-c95d-4488-8a07-9aff189551b9", "metadata": {}, "outputs": [], "source": [ "# stream_ids = '(5, 7, 8, 9, 12, 17, 18, 19, 20, 21)'\n", - "stream_id = 1\n", - "TOIA_ID = 1" + "stream_id = 721\n", + "# TOIA_ID = 1" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 73, "id": "e12b33cc-d96d-496e-bb70-c04dccfc4149", "metadata": {}, "outputs": [], "source": [ - "sql_url = \"mysql+mysqlconnector://root:anypasswords@localhost:3307/toia\"\n", + "sql_url = \"{dbconnection}://{dbusername}:{dbpassword}@{dbhost}:{dbport}/{dbname}\".format(\n", + " dbconnection=config[\"DB_CONNECTION\"], \n", + " dbusername=config[\"DB_USERNAME\"], \n", + " dbpassword=config[\"DB_PASSWORD\"], \n", + " dbhost=config[\"DB_HOST\"], \n", + " dbport=config[\"DB_PORT\"],\n", + " dbname=config[\"DB_DATABASE\"]\n", + ")\n", + "\n", + "# sql_url = \"mysql+mysqlconnector://root:root@localhost:3307/toia\"\n", "\n", "engine = db.create_engine(sql_url)\n", "connection = engine.connect()\n", - "metadata = db.MetaData()\n", - "\n", - "# statement = text(f\"\"\"\n", - "# SELECT vqs.id_stream as stream_id_stream, \n", - "# vqs.type, q.question, v.answer, v.id_video, q.id\n", - "# FROM video v\n", - "# JOIN videos_questions_streams vqs ON vqs.id_video = v.id_video\n", - "# JOIN questions q ON q.id = vqs.id_question\n", - "# WHERE vqs.id_stream in {stream_ids}\n", - "# AND v.private = 0 \n", - "# AND vqs.type NOT IN ('filler');\"\"\")\n", "\n", "statement = text(f\"\"\"\n", " SELECT vqs.id_stream as stream_id_stream, \n", - " vqs.type, q.question, v.answer, v.id_video\n", + " vqs.type, vqs.ada_search, q.question, v.answer, v.id_video\n", " FROM video v\n", " JOIN videos_questions_streams vqs ON vqs.id_video = v.id_video\n", " JOIN questions q ON q.id = vqs.id_question\n", @@ -139,33 +133,24 @@ " AND v.private = 0 \n", " AND vqs.type NOT IN ('filler');\"\"\")\n", "\n", - "# ResultProxy = connection.execute(avatar_kb)\n", - "ResultProxy = connection.execute(statement)\n", - "ResultSet = ResultProxy.fetchall()\n", - "\n", - "# df_avatar = pd.DataFrame(ResultSet, \n", - "# columns=[\n", - "# 'stream_id_stream',\n", - "# 'type',\n", - "# 'question',\n", - "# 'answer',\n", - "# 'id_video',\n", - "# 'id_question',\n", - "# ])\n", + "result_proxy = connection.execute(statement)\n", + "result_set = result_proxy.fetchall()\n", "\n", - "df_avatar = pd.DataFrame(ResultSet, \n", - " columns=[\n", - " 'stream_id_stream',\n", - " 'type',\n", - " 'question',\n", - " 'answer',\n", - " 'id_video',\n", - " ])" + "df_avatar = pd.DataFrame(\n", + " result_set,\n", + " columns=[\n", + " 'stream_id_stream',\n", + " 'type',\n", + " 'ada_search',\n", + " 'question',\n", + " 'answer',\n", + " 'id_video'\n", + " ])" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 74, "id": "4f9f0827-9536-4eed-b240-2c4461566bf7", "metadata": {}, "outputs": [ @@ -192,6 +177,7 @@ " \n", " stream_id_stream\n", " type\n", + " ada_search\n", " question\n", " answer\n", " id_video\n", @@ -200,198 +186,99 @@ " \n", " \n", " 0\n", - " 1\n", - " no-answer\n", - " Say: I don't have an answer to that right now.\n", - " I don't.\n", - " Alberto_1_11_6cfcb663.mp4\n", + " 721\n", + " y/n-answer\n", + " [0.0020983361,0.030330962,0.0019777715,0.05068...\n", + " Record yourself saying No. You can add questio...\n", + " .\n", + " Soojin _41_3867_38623030.mp4\n", " \n", " \n", " 1\n", - " 1\n", + " 721\n", " no-answer\n", - " Say: Sorry, I didn't record answers to that qu...\n", - " Sorry,.\n", - " Alberto_1_12_56020feb.mp4\n", + " [-0.0008500544,0.00853398,0.029238785,0.043580...\n", + " Say: I don't have an answer to that right now.\n", + " .\n", + " Soojin _41_3869_517398f6.mp4\n", " \n", " \n", " 2\n", - " 1\n", + " 721\n", " no-answer\n", - " Say: Can you try rephrasing the question?\n", - " Didn't get it,.\n", - " Alberto_1_13_fecb8829.mp4\n", + " [-0.00058192614,0.016313002,0.0016513861,0.041...\n", + " Say: Sorry, I didn't record answers to that qu...\n", + " .\n", + " Soojin _41_3870_77c1cb8e.mp4\n", " \n", " \n", " 3\n", - " 1\n", + " 721\n", " greeting\n", + " [-0.0010867842,0.027357157,0.010503011,0.04084...\n", " Record a greeting (e.g., hello, hi)\n", - " Hi.\n", - " Alberto_1_14_44a17e28.mp4\n", + " .\n", + " Soojin _41_3872_45bdf208.mp4\n", " \n", " \n", " 4\n", - " 1\n", + " 721\n", " exit\n", + " [0.013473576,0.023772048,0.0075254096,0.042554...\n", " Record a goodbye (or end of conversation sente...\n", - " Go away.\n", - " Alberto_1_15_b37dded9.mp4\n", - " \n", - " \n", - " 5\n", - " 1\n", - " answer\n", - " What is your name?\n", - " My name is Alberto.\n", - " Alberto_1_16_9c461b90.mp4\n", - " \n", - " \n", - " 6\n", - " 1\n", - " answer\n", - " Where and when were you born?\n", - " Milan. Italy, 1985.\n", - " Alberto_1_17_e0d7e418.mp4\n", - " \n", - " \n", - " 7\n", - " 1\n", - " answer\n", - " What do you do for a living?\n", - " I do what they do.\n", - " Alberto_1_18_85b921a9.mp4\n", - " \n", - " \n", - " 8\n", - " 1\n", - " y/n-answer\n", - " Record yourself saying Yes. You cann add quest...\n", - " haha.\n", - " Alberto_1_19_f63f6b5c.mp4\n", - " \n", - " \n", - " 9\n", - " 1\n", - " y/n-answer\n", - " Record yourself saying No. You can add questio...\n", - " nana.\n", - " Alberto_1_20_113d66fc.mp4\n", - " \n", - " \n", - " 10\n", - " 1\n", - " answer\n", - " What is your job?\n", - " and a CFO and the analytics ahead of a seed st...\n", - " Alberto_1_21_0813c283.mp4\n", - " \n", - " \n", - " 11\n", - " 1\n", - " answer\n", - " Whici startup?\n", - " It is called Around. It is a sort of Airbnb fo...\n", - " Alberto_1_22_7c4398b9.mp4\n", - " \n", - " \n", - " 12\n", - " 1\n", - " no-answer\n", - " Cannot Answer\n", - " Either, I didn't get you a question or I do no...\n", - " Alberto_1_23_8edf7438.mp4\n", + " .\n", + " Soojin _41_3873_0382a76b.mp4\n", " \n", " \n", "\n", "
" ], "text/plain": [ - " stream_id_stream type \\\n", - "0 1 no-answer \n", - "1 1 no-answer \n", - "2 1 no-answer \n", - "3 1 greeting \n", - "4 1 exit \n", - "5 1 answer \n", - "6 1 answer \n", - "7 1 answer \n", - "8 1 y/n-answer \n", - "9 1 y/n-answer \n", - "10 1 answer \n", - "11 1 answer \n", - "12 1 no-answer \n", + " stream_id_stream type \\\n", + "0 721 y/n-answer \n", + "1 721 no-answer \n", + "2 721 no-answer \n", + "3 721 greeting \n", + "4 721 exit \n", "\n", - " question \\\n", - "0 Say: I don't have an answer to that right now. \n", - "1 Say: Sorry, I didn't record answers to that qu... \n", - "2 Say: Can you try rephrasing the question? \n", - "3 Record a greeting (e.g., hello, hi) \n", - "4 Record a goodbye (or end of conversation sente... \n", - "5 What is your name? \n", - "6 Where and when were you born? \n", - "7 What do you do for a living? \n", - "8 Record yourself saying Yes. You cann add quest... \n", - "9 Record yourself saying No. You can add questio... \n", - "10 What is your job? \n", - "11 Whici startup? \n", - "12 Cannot Answer \n", + " ada_search \\\n", + "0 [0.0020983361,0.030330962,0.0019777715,0.05068... \n", + "1 [-0.0008500544,0.00853398,0.029238785,0.043580... \n", + "2 [-0.00058192614,0.016313002,0.0016513861,0.041... \n", + "3 [-0.0010867842,0.027357157,0.010503011,0.04084... \n", + "4 [0.013473576,0.023772048,0.0075254096,0.042554... \n", "\n", - " answer \\\n", - "0 I don't. \n", - "1 Sorry,. \n", - "2 Didn't get it,. \n", - "3 Hi. \n", - "4 Go away. \n", - "5 My name is Alberto. \n", - "6 Milan. Italy, 1985. \n", - "7 I do what they do. \n", - "8 haha. \n", - "9 nana. \n", - "10 and a CFO and the analytics ahead of a seed st... \n", - "11 It is called Around. It is a sort of Airbnb fo... \n", - "12 Either, I didn't get you a question or I do no... \n", + " question answer \\\n", + "0 Record yourself saying No. You can add questio... . \n", + "1 Say: I don't have an answer to that right now. . \n", + "2 Say: Sorry, I didn't record answers to that qu... . \n", + "3 Record a greeting (e.g., hello, hi) . \n", + "4 Record a goodbye (or end of conversation sente... . \n", "\n", - " id_video \n", - "0 Alberto_1_11_6cfcb663.mp4 \n", - "1 Alberto_1_12_56020feb.mp4 \n", - "2 Alberto_1_13_fecb8829.mp4 \n", - "3 Alberto_1_14_44a17e28.mp4 \n", - "4 Alberto_1_15_b37dded9.mp4 \n", - "5 Alberto_1_16_9c461b90.mp4 \n", - "6 Alberto_1_17_e0d7e418.mp4 \n", - "7 Alberto_1_18_85b921a9.mp4 \n", - "8 Alberto_1_19_f63f6b5c.mp4 \n", - "9 Alberto_1_20_113d66fc.mp4 \n", - "10 Alberto_1_21_0813c283.mp4 \n", - "11 Alberto_1_22_7c4398b9.mp4 \n", - "12 Alberto_1_23_8edf7438.mp4 " + " id_video \n", + "0 Soojin _41_3867_38623030.mp4 \n", + "1 Soojin _41_3869_517398f6.mp4 \n", + "2 Soojin _41_3870_77c1cb8e.mp4 \n", + "3 Soojin _41_3872_45bdf208.mp4 \n", + "4 Soojin _41_3873_0382a76b.mp4 " ] }, - "execution_count": 29, + "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "df_avatar" - ] - }, - { - "cell_type": "code", - "execution_count": 541, - "id": "9047b1ea-dd28-4c33-9d4a-e70f306a7274", - "metadata": {}, - "outputs": [], - "source": [ - "df_first_10 = df_avatar.sort_values(by=['stream_id_stream', 'id_question']).groupby('stream_id_stream').nth(range(10, 20))\n", - "df_avatar = df_first_10.reset_index().copy()" + "df_avatar.head()" ] }, { "cell_type": "markdown", "id": "29fcfdba-16a1-4f17-a317-33f0d8d102c4", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true, + "tags": [] + }, "source": [ "## NLP analysis with spaCy" ] @@ -483,14 +370,16 @@ { "cell_type": "markdown", "id": "65ca9fce-b406-4fad-a898-bd30768941dd", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## helper functions" ] }, { "cell_type": "code", - "execution_count": 371, + "execution_count": 7, "id": "d4b14530-483d-4437-a5ad-36ec9df0fd3c", "metadata": {}, "outputs": [], @@ -511,7 +400,7 @@ }, { "cell_type": "code", - "execution_count": 372, + "execution_count": 8, "id": "bebbb785-0e26-4938-a4e6-5bbe15691122", "metadata": {}, "outputs": [], @@ -528,7 +417,7 @@ }, { "cell_type": "code", - "execution_count": 373, + "execution_count": null, "id": "0bb204c7-cd82-4bc9-bec4-d0e3d87f0cf6", "metadata": {}, "outputs": [], @@ -641,7 +530,9 @@ { "cell_type": "markdown", "id": "485590a0-eeff-4fd8-a502-8c73f5ebd3a2", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "### testing update helper functions" ] @@ -1041,7 +932,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 76, "id": "85c5cd4b-6918-4d87-93fa-e9063a78b59d", "metadata": {}, "outputs": [], @@ -1052,8 +943,8 @@ "import pandas as pd\n", "import numpy as np\n", "\n", - "config = dotenv_values()\n", - "openai.organization = config['YOUR_ORG_ID']\n", + "config = dotenv_values(\".env\")\n", + "# openai.organization = config['YOUR_ORG_ID']\n", "openai.api_key = config['OPENAI_API_KEY']\n", "# openai.Model.list()" ] @@ -1066,6 +957,16 @@ "### embed" ] }, + { + "cell_type": "markdown", + "id": "d061e8c2-23f3-4c1f-8a77-1ac89d53666e", + "metadata": {}, + "source": [ + "**Run the below only the first time**\n", + "\n", + "Uncomment if needed." + ] + }, { "cell_type": "code", "execution_count": 31, @@ -1148,18 +1049,8 @@ } ], "source": [ - "df_avatar['combined'] = \"Question: \" + df_avatar.question.str.strip() + \"; Answer: \" + df_avatar.answer.str.strip()\n", - "df_avatar.head(2)" - ] - }, - { - "cell_type": "markdown", - "id": "d061e8c2-23f3-4c1f-8a77-1ac89d53666e", - "metadata": {}, - "source": [ - "**Run the below only the first time**\n", - "\n", - "Uncomment if needed." + "# df_avatar['combined'] = \"Question: \" + df_avatar.question.str.strip() + \"; Answer: \" + df_avatar.answer.str.strip()\n", + "# df_avatar.head(2)" ] }, { @@ -1184,6 +1075,16 @@ "# df_avatar.to_csv(f\"\"\"output/embedded_1k_toia_id_{TOIA_ID}.csv\"\"\")" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "577da783-527b-4efa-bdbe-0b9731307307", + "metadata": {}, + "outputs": [], + "source": [ + "# df = pd.read_csv(f\"\"\"output/embedded_1k_toia_id_{TOIA_ID}.csv\"\"\")" + ] + }, { "cell_type": "markdown", "id": "af706d5e-b93c-4251-b447-57f4e35d439c", @@ -1194,18 +1095,19 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 77, "id": "cc4e6bc6-62ad-4abf-b131-6e026a7b2993", "metadata": {}, "outputs": [], "source": [ - "df = pd.read_csv(f\"\"\"output/embedded_1k_toia_id_{TOIA_ID}.csv\"\"\")\n", + "df = df_avatar.copy()\n", + "\n", "df['ada_search'] = df.ada_search.apply(eval).apply(np.array) #needed if np array stored as txt" ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 78, "id": "ee70cadc-41f6-4c48-ba4d-d720ef3eba4d", "metadata": {}, "outputs": [], @@ -1224,7 +1126,78 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 228, + "id": "c03988e0-895b-434a-bf94-d34039793de7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def toia_answer(query, data, engine, interactor_id=41, memory=10, minutes=15):\n", + " print(\"toia_answer\")\n", + " print(query)\n", + " embedding = get_embedding(query, engine='text-search-ada-query-001')\n", + " print(\"API worked\")\n", + " data['similarities'] = data.ada_search.apply(\n", + " lambda x: cosine_similarity(x, embedding))\n", + " res = data.sort_values('similarities', ascending=False)\n", + "\n", + " ada_similarity_scores = res.similarities.values\n", + " \n", + " # (removed thresholding. Keep every answer)\n", + " if ada_similarity_scores[0] > 0: \n", + " # take first 10 answers from the chatlog\"\n", + " # connect to the db\n", + " engine = db.create_engine(sql_url)\n", + " connection = engine.connect()\n", + " # retrieve the video IDs already played. The logic here is \n", + " # to retrieve the top {memory} last logs from the db and take the ones\n", + " # that aren't older than {minutes}\n", + " statement = text(f\"\"\"\n", + " SELECT cl.video_played,\n", + " (cl.timestamp - LAG(cl.timestamp, 1) OVER (ORDER BY cl.timestamp)) / 60000 AS time_diff_min\n", + " FROM (SELECT * FROM conversations_log WHERE question_asked != \"None\") cl\n", + " WHERE cl.interactor_id = {interactor_id}\n", + " ORDER BY cl.timestamp DESC\n", + " LIMIT {memory}\n", + " ;\"\"\")\n", + " result_proxy = connection.execute(statement)\n", + " result_set = result_proxy.fetchall()\n", + " # put them in a df\n", + " chatlog = pd.DataFrame(result_set)\n", + " # take cumsum of top minute differences -- only problem to highlith here:\n", + " # WE DON'T HAVE A NOTION OF WHAT TIME IS NOW\n", + " chatlog['cum_time_diff_min'] = chatlog['time_diff_min'].cumsum()\n", + " \n", + " # define the temp cache of played video IDs -- adding here the notion of NOW: first question of new\n", + " # session is going to be put in the cache regardless. Then if time lag is too long from first and last,\n", + " # the cache is just that. Then it starts populating.\n", + " if chatlog['cum_time_diff_min'][0] >= minutes:\n", + " video_cache = chatlog['video_played'].values[0]\n", + " else: \n", + " video_cache = chatlog[chatlog['cum_time_diff_min'] < minutes]['video_played'].values\n", + " \n", + " # store outputs (already sorted by decreasing order of retrieval)\n", + " answers = res['answer'].values\n", + " videos = res['id_video'].values\n", + " scores = ada_similarity_scores\n", + " if len(set(video_cache).intersection(set(videos))) > 0:\n", + " next_best_idx = next(x for x, vid in enumerate(videos) if vid not in video_cache)\n", + " return answers[next_best_idx], videos[next_best_idx], scores[next_best_idx]\n", + " else:\n", + " return answers[0], videos[0], scores[0]\n", + " else:\n", + " df_noanswers = data[data['type'] == \"no-answer\"]\n", + " if df_noanswers.shape[0] > 0:\n", + " answers = df_noanswers.sample(n=1)\n", + " return answers['answer'].values[0], answers['id_video'].values[0], ada_similarity_score\n", + " else:\n", + " return \"You haven't recorded no-answers\", \"204\", \"No Content\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 247, "id": "fbd9d141-882b-47a0-bd59-af91ccfcc3a9", "metadata": {}, "outputs": [ @@ -1232,24 +1205,94 @@ "name": "stdout", "output_type": "stream", "text": [ - "Record a greeting (e.g., hello, hi): Hi.\n", - "\n", - "Whici startup?: It is called Around. It is a sort of Airbnb for Office Space.\n", - "\n", - "Record yourself saying Yes. You cann add questions for this answer later.: haha.\n", - "\n" + "toia_answer\n", + "Are you afraid of death?\n", + "API worked\n" ] } ], "source": [ - "res = answer_retrieval(df, \"Hi!\", n=3)" + "res = toia_answer(\"Are you afraid of death?\", df)" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 248, "id": "e54ef8fe-9f94-4ec7-ba2b-163e6f1cc333", "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(\"[Brian] I'm not dying, I'm just losing my ability to live.\\r\\nYeah, take advantage of it.\\r\\nLife isn't guaranteed to any of us.\",\n", + " 'Soojin _41_3892_c12c6f9f.mp4',\n", + " 0.3665458862681638)" + ] + }, + "execution_count": 248, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "res" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1bb2971-b288-4044-9c93-92e05e331f15", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc8b89af-eaf1-4845-9fa5-1e6188d22e2e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 249, + "id": "8b3c2672-603e-4af0-9280-274ef8b32da2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# We need to know the interactor_id\n", + "interactor_id = 41\n", + "memory = 10\n", + "minutes = 15\n", + "# engine = db.create_engine(sql_url)\n", + "connection = engine.connect()\n", + "\n", + "statement = text(f\"\"\"\n", + " SELECT *, cl.video_played,\n", + " (cl.timestamp - LAG(cl.timestamp, 1) OVER (ORDER BY cl.timestamp)) / 60000 AS time_diff_min\n", + " FROM (SELECT * FROM conversations_log WHERE question_asked != \"None\") cl\n", + " WHERE cl.interactor_id = {interactor_id}\n", + " ORDER BY cl.timestamp DESC\n", + " LIMIT {memory}\n", + " ;\"\"\")\n", + "\n", + "result_proxy = connection.execute(statement)\n", + "result_set = result_proxy.fetchall()\n", + "\n", + "chatlog = pd.DataFrame(result_set)\n", + "chatlog['cum_time_diff_min'] = chatlog['time_diff_min'].cumsum()" + ] + }, + { + "cell_type": "code", + "execution_count": 250, + "id": "39608674-6003-4a39-9849-9d299bcb1ff6", + "metadata": { + "tags": [] + }, "outputs": [ { "data": { @@ -1272,108 +1315,105 @@ " \n", " \n", " \n", - " Unnamed: 0\n", - " stream_id_stream\n", - " type\n", - " question\n", - " answer\n", - " id_video\n", - " combined\n", - " ada_similarity\n", - " ada_search\n", - " similarities\n", + " interactor_id\n", + " toia_id\n", + " timestamp\n", + " filler\n", + " question_asked\n", + " video_played\n", + " ada_similarity_score\n", + " mode\n", + " video_played\n", + " time_diff_min\n", + " cum_time_diff_min\n", " \n", " \n", " \n", " \n", - " 3\n", - " 3\n", - " 1\n", - " greeting\n", - " Record a greeting (e.g., hello, hi)\n", - " Hi.\n", - " Alberto_1_14_44a17e28.mp4\n", - " Question: Record a greeting (e.g., hello, hi);...\n", - " [0.0024500133004039526, 0.014042790979146957, ...\n", - " [-0.0015507441712543368, 0.03209500387310982, ...\n", - " 0.339486\n", + " 0\n", + " 41\n", + " 41\n", + " 1678882528525\n", + " 0\n", + " JJ, are you afraid of death\n", + " Soojin _41_3900_503a1862.mp4\n", + " 0.431172\n", + " SEARCH\n", + " Soojin _41_3900_503a1862.mp4\n", + " 0.4515\n", + " 0.4515\n", " \n", " \n", - " 11\n", - " 11\n", - " 1\n", - " answer\n", - " Whici startup?\n", - " It is called Around. It is a sort of Airbnb fo...\n", - " Alberto_1_22_7c4398b9.mp4\n", - " Question: Whici startup?; Answer: It is called...\n", - " [0.005293071269989014, 0.021358370780944824, -...\n", - " [0.0287274569272995, 0.046579521149396896, 0.0...\n", - " 0.286512\n", + " 1\n", + " 41\n", + " 41\n", + " 1678882501438\n", + " 0\n", + " Brian, are you afraid of death?\n", + " Soojin _41_3881_760068fc.mp4\n", + " 0.430592\n", + " SEARCH\n", + " Soojin _41_3881_760068fc.mp4\n", + " 0.8085\n", + " 1.2600\n", " \n", " \n", - " 8\n", - " 8\n", - " 1\n", - " y/n-answer\n", - " Record yourself saying Yes. You cann add quest...\n", - " haha.\n", - " Alberto_1_19_f63f6b5c.mp4\n", - " Question: Record yourself saying Yes. You cann...\n", - " [0.0019218891393393278, 0.013361705467104912, ...\n", - " [-0.0023853471502661705, 0.041393689811229706,...\n", - " 0.253179\n", + " 2\n", + " 41\n", + " 41\n", + " 1678882452926\n", + " 0\n", + " are you afraid of death\n", + " Soojin _41_3918_5f56b7e3.mp4\n", + " 0.422513\n", + " SEARCH\n", + " Soojin _41_3918_5f56b7e3.mp4\n", + " 0.6283\n", + " 1.8883\n", " \n", " \n", "\n", "

" ], "text/plain": [ - " Unnamed: 0 stream_id_stream type \\\n", - "3 3 1 greeting \n", - "11 11 1 answer \n", - "8 8 1 y/n-answer \n", - "\n", - " question \\\n", - "3 Record a greeting (e.g., hello, hi) \n", - "11 Whici startup? \n", - "8 Record yourself saying Yes. You cann add quest... \n", - "\n", - " answer \\\n", - "3 Hi. \n", - "11 It is called Around. It is a sort of Airbnb fo... \n", - "8 haha. \n", - "\n", - " id_video \\\n", - "3 Alberto_1_14_44a17e28.mp4 \n", - "11 Alberto_1_22_7c4398b9.mp4 \n", - "8 Alberto_1_19_f63f6b5c.mp4 \n", + " interactor_id toia_id timestamp filler \\\n", + "0 41 41 1678882528525 0 \n", + "1 41 41 1678882501438 0 \n", + "2 41 41 1678882452926 0 \n", "\n", - " combined \\\n", - "3 Question: Record a greeting (e.g., hello, hi);... \n", - "11 Question: Whici startup?; Answer: It is called... \n", - "8 Question: Record yourself saying Yes. You cann... \n", + " question_asked video_played \\\n", + "0 JJ, are you afraid of death Soojin _41_3900_503a1862.mp4 \n", + "1 Brian, are you afraid of death? Soojin _41_3881_760068fc.mp4 \n", + "2 are you afraid of death Soojin _41_3918_5f56b7e3.mp4 \n", "\n", - " ada_similarity \\\n", - "3 [0.0024500133004039526, 0.014042790979146957, ... \n", - "11 [0.005293071269989014, 0.021358370780944824, -... \n", - "8 [0.0019218891393393278, 0.013361705467104912, ... \n", + " ada_similarity_score mode video_played time_diff_min \\\n", + "0 0.431172 SEARCH Soojin _41_3900_503a1862.mp4 0.4515 \n", + "1 0.430592 SEARCH Soojin _41_3881_760068fc.mp4 0.8085 \n", + "2 0.422513 SEARCH Soojin _41_3918_5f56b7e3.mp4 0.6283 \n", "\n", - " ada_search similarities \n", - "3 [-0.0015507441712543368, 0.03209500387310982, ... 0.339486 \n", - "11 [0.0287274569272995, 0.046579521149396896, 0.0... 0.286512 \n", - "8 [-0.0023853471502661705, 0.041393689811229706,... 0.253179 " + " cum_time_diff_min \n", + "0 0.4515 \n", + "1 1.2600 \n", + "2 1.8883 " ] }, - "execution_count": 37, + "execution_count": 250, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "res" + "chatlog[chatlog['cum_time_diff_min'] < minutes]" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "d230fd73-d450-4789-99bc-8e1cdf5d2a00", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "id": "95b386e7-e73a-43b4-b668-434707333263", @@ -2276,7 +2316,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.9" + "version": "3.9.16" } }, "nbformat": 4, diff --git a/server/toia-dm/old_toia-dm_files/toia-capstone-2021-1b8f8a91c797.json b/server/toia-dm/old_toia-dm_files/toia-capstone-2021-1b8f8a91c797.json new file mode 100644 index 00000000..1bd29cf0 --- /dev/null +++ b/server/toia-dm/old_toia-dm_files/toia-capstone-2021-1b8f8a91c797.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "toia-capstone-2021", + "private_key_id": "1b8f8a91c797ef805e147f681408f40f3862846f", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDESOBk7yeufif\nWIPM3W5k2Ut44iO2+5siSX9X9MZlIVjLhYRZuq4mrDGRGE5HhlNbyQtCStkHK6Pa\nJwP5isMuou3Ol87INM1dEPYjim7JFBqVD8olNjNHojNVmg8Xc5AdSY26uicQv/ji\nqnwpu68w1bIEDKaz6djigoeS3wi9BBvrqKdLBvsKN1Fq6pUb0utFyGyREZpGF9QO\nPeW9eLyrLdRxU4k74hyquN17olhfA3K8X+SxyHca+UEjHmAhDcnn59TMcbUdKom4\niKCWcCj/licvoYBj/HA9rAo7L09obNWv9Bov6uhvqD51uB4W421Ro6EcgJ1vqb1r\nJHT6KFjjAgMBAAECggEAAgW9UwZcky3cPF4g/1Cc+usrWZPtf+oH/qFwWfAaLGIm\n1xmJtct/Q5eqrsAw22NdeQc9TDct+DC6o6Tn0NW1zWQHJq8kJjlt7GjCj/igjYPp\nmzazUHQesNgnVehkkUhL37BWQHX6rHhnKIXjT/OlfIk55KUj+zo2WwP9oIQmy5DH\nTzg76xus3JdIiEZa65hAHB53r/16PBz4Yos2rwhM74DpSfzW/oShylJPe1vxz6uJ\nQHGl1G2EKc5BtT+1n/Yp1lfrzk7ip8uGYiM1os1wTJfDd+9pBXCh7bNd4O3nrRen\nzoJjEGIgxreOkAmuhTSUnFPsfVI+n/yKiS2CxvTmCQKBgQDzLOj/TFutgG6DLdmN\nANW033lLCWfjfOxFaHpy/X3PpbZf68v8hpgBtJqX+/inylNzu4GKigYOYTMABcod\nOWcjuXbbgmSIOw0HjdU09PcORotlriu8Ku8Qel05Pwu7f/VEE8g2JxzBOeUuHYX2\n+o0gWnjBzCwr42hOF15EWZvKHQKBgQDNWrhfvjwUgtu9oVPJ0LPx9Yeg2J9QLI7H\n1EGc/3qyGu2MlSlP/JD7S40YZNeExUQzQUvwtKCVOT+X1Iv2PcsuV4uhtGV6Bjv6\nEWn3NQcNE2ez91L+zYTP27jTouRuO6RjNNSEdacW3s1UEB/k00wW+s40QwKZXI9I\nCOOKWnM+/wKBgDQP5mZaI7qWp5JgK5Z7mkVaHu7IubxDj5Ygky6xRNFDCjOpGQgc\njMi4sOxfHtJVh95cQ5S0ji1f8/pKQwZUttc4KtE+LmXYNqbqX74xv/8HbWq8ilKD\n4qDlgbXy4IEpGQqVLaUiZI6d5Yok+fxxketeU0IygzZ2PmuRpzX89VSlAoGAfRUo\nPvFSCTBYBp6wuboNEXF75oZsK9qoMaHhQW8AXmI2la35lwYBjX/MdrJd8Dp0O0An\nsHtlYN+ZE7NceWXUT+r1W07VjMklWUGoKPK808MhJKaegCPRJQbyAr8yos5jaCYy\n2GgNmcmmifC0bJ4jt8+XMJAskhumDruTVZ+YmSMCgYAOQX/NYldkZuHXZGu3XZ0Y\nIjMuM//gTlGgKsbkSA61Tjv9CNyKSps99R8VRvTiBpa3QXGVvTvp2ffXsvn41way\nci/LzVkKYBqtS0Anu2EoNy7EbzrtcCOivzZ0vAOFuengD6CkHKeFC7/JCOggLugS\nX/3BJ6UHVn6VtE7xAkTioA==\n-----END PRIVATE KEY-----\n", + "client_email": "toia-cloudconnect@toia-capstone-2021.iam.gserviceaccount.com", + "client_id": "102222036243607638338", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/toia-cloudconnect%40toia-capstone-2021.iam.gserviceaccount.com" +} diff --git a/server/toia-dm/requirements.txt b/server/toia-dm/requirements.txt index 01c682eb..766517b1 100644 --- a/server/toia-dm/requirements.txt +++ b/server/toia-dm/requirements.txt @@ -1,77 +1,94 @@ -aiohttp==3.8.1 -aiosignal==1.2.0 -anyio==3.5.0 -asgiref==3.5.0 -async-timeout==4.0.2 -attrs==21.4.0 -blis==0.7.5 -cachetools==5.0.0 -catalogue==2.0.6 -certifi==2021.10.8 -cffi==1.15.0 -charset-normalizer==2.0.12 -click==8.0.4 +aiohttp==3.8.6 +aiosignal==1.3.1 +anyio==3.7.1 +async-timeout==4.0.3 +attrs==23.1.0 +blinker==1.7.0 +blis==0.7.11 +cachetools==5.3.2 +catalogue==2.0.10 +certifi==2023.7.22 +cffi==1.16.0 +charset-normalizer==3.3.2 +click==8.1.7 cloud-sql-python-connector==0.5.1 -cryptography==36.0.1 -cymem==2.0.6 -fastapi==0.74.0 -frozenlist==1.3.0 +confection==0.1.3 +contourpy==1.1.1 +cryptography==41.0.5 +cycler==0.12.1 +cymem==2.0.8 +exceptiongroup==1.1.3 +fastapi==0.104.1 +flask==3.0.0 +fonttools==4.44.3 +friendlywords==1.1.2 +frozenlist==1.4.0 google-api-core==2.5.0 google-api-python-client==2.37.0 google-auth==2.6.0 google-auth-httplib2==0.1.0 googleapis-common-protos==1.54.0 -greenlet==1.1.2 -h11==0.13.0 -httplib2==0.20.4 -idna==3.3 -Jinja2==3.0.3 -joblib==1.1.0 +greenlet==3.0.1 +h11==0.14.0 +httplib2==0.22.0 +idna==3.4 +importlib-metadata==6.8.0 +importlib-resources==6.1.1 +itsdangerous==2.1.2 +Jinja2==3.1.2 +joblib==1.3.2 +kiwisolver==1.4.5 langcodes==3.3.0 -MarkupSafe==2.1.0 -matplotlib -multidict==6.0.2 -murmurhash==1.0.6 -nltk==3.7 -numpy==1.22.2 -openai -packaging==21.3 -pandas==1.4.1 -pathy==0.6.1 -plotly -preshed==3.0.6 -protobuf==3.19.4 -pyasn1==0.4.8 -pyasn1-modules==0.2.8 +MarkupSafe==2.1.3 +matplotlib==3.7.3 +multidict==6.0.4 +murmurhash==1.0.10 +mysqlclient==1.4.4 +nltk==3.8.1 +numpy==1.24.4 +openai==0.27.8 +packaging==23.2 +pandas==2.0.3 +pathy==0.10.3 +Pillow==10.1.0 +plotly==5.18.0 +preshed==3.0.9 +protobuf==4.21.12 +pyasn1==0.5.0 +pyasn1-modules==0.3.0 pycparser==2.21 -pydantic==1.8.2 -pyOpenSSL==22.0.0 -pyparsing==3.0.7 +pydantic==1.10.6 +PyMySQL==1.1.0 +pyOpenSSL==23.3.0 +pyparsing==3.1.1 python-dateutil==2.8.2 -python-dotenv==0.19.2 -pytz==2021.3 -regex==2022.1.18 -requests==2.27.1 -rsa==4.8 -scikit-learn==1.0.2 -scipy==1.8.0 +python-dotenv==1.0.0 +pytz==2023.3.post1 +regex==2023.10.3 +requests==2.31.0 +rsa==4.9 +scikit-learn==1.2.1 +scipy==1.10.1 six==1.16.0 -sklearn==0.0 -smart-open==5.2.1 -sniffio==1.2.0 -spacy==3.2.2 -spacy-legacy==3.0.8 -spacy-loggers==1.0.1 +smart-open==6.4.0 +sniffio==1.3.0 +spacy==3.5.1 +spacy-legacy==3.0.12 +spacy-loggers==1.0.5 SQLAlchemy==1.4.31 -srsly==2.4.2 -starlette==0.17.1 -thinc==8.0.13 -threadpoolctl==3.1.0 -tqdm==4.62.3 -typer==0.4.0 -typing-extensions==4.1.1 +srsly==2.4.8 +starlette==0.27.0 +tenacity==8.2.3 +thinc==8.1.12 +threadpoolctl==3.2.0 +tqdm==4.66.1 +typer==0.7.0 +typing-extensions==4.8.0 +tzdata==2023.3 uritemplate==4.1.1 -urllib3==1.26.8 -uvicorn==0.17.5 -wasabi==0.9.0 -yarl==1.7.2 \ No newline at end of file +urllib3==2.1.0 +uvicorn==0.24.0.post1 +wasabi==1.1.2 +werkzeug==3.0.1 +yarl==1.9.2 +zipp==3.17.0 diff --git a/server/toia-dm/requirements_old.txt b/server/toia-dm/requirements_old.txt new file mode 100644 index 00000000..581414c1 --- /dev/null +++ b/server/toia-dm/requirements_old.txt @@ -0,0 +1,76 @@ +aiohttp==3.8.1 +aiosignal==1.2.0 +anyio==3.5.0 +asgiref==3.5.0 +async-timeout==4.0.2 +attrs==21.4.0 +blis==0.7.5 +cachetools==5.0.0 +catalogue==2.0.6 +certifi==2021.10.8 +cffi==1.15.0 +charset-normalizer==2.0.12 +click==8.0.4 +cloud-sql-python-connector==0.5.1 +cryptography==36.0.1 +cymem==2.0.6 +fastapi==0.74.0 +frozenlist==1.3.0 +google-api-core==2.5.0 +google-api-python-client==2.37.0 +google-auth==2.6.0 +google-auth-httplib2==0.1.0 +googleapis-common-protos==1.54.0 +greenlet==1.1.2 +h11==0.13.0 +httplib2==0.20.4 +idna==3.3 +Jinja2==3.0.3 +joblib==1.1.0 +langcodes==3.3.0 +MarkupSafe==2.1.0 +matplotlib +multidict==6.0.2 +murmurhash==1.0.6 +nltk==3.7 +numpy==1.22.2 +openai +packaging==21.3 +pandas==1.4.1 +pathy==0.6.1 +plotly +preshed==3.0.6 +protobuf==3.19.4 +pyasn1==0.4.8 +pyasn1-modules==0.2.8 +pycparser==2.21 +pydantic==1.8.2 +pyOpenSSL==22.0.0 +pyparsing==3.0.7 +python-dateutil==2.8.2 +python-dotenv==0.19.2 +pytz==2021.3 +regex==2022.1.18 +requests==2.27.1 +rsa==4.8 +scikit-learn==1.0.2 +scipy==1.8.0 +six==1.16.0 +sklearn==0.0 +smart-open==5.2.1 +sniffio==1.2.0 +spacy==3.2.2 +spacy-legacy==3.0.8 +spacy-loggers==1.0.1 +SQLAlchemy==1.4.31 +srsly==2.4.2 +starlette==0.17.1 +thinc==8.0.13 +threadpoolctl==3.1.0 +tqdm==4.62.3 +typer==0.4.0 +uritemplate==4.1.1 +urllib3==1.26.8 +uvicorn==0.17.5 +wasabi==0.9.0 +yarl==1.7.2 \ No newline at end of file diff --git a/server/toia-dm/utils_gpt3.py b/server/toia-dm/utils_gpt3.py index a701ae52..766f6236 100644 --- a/server/toia-dm/utils_gpt3.py +++ b/server/toia-dm/utils_gpt3.py @@ -4,6 +4,8 @@ import pandas as pd import numpy as np import os +from sqlalchemy.sql import text + config = dotenv_values() # openai.organization = config['YOUR_ORG_ID'] @@ -13,19 +15,69 @@ openai.api_key = os.environ.get("OPENAI_API_KEY") # openai.Model.list() +print("toia_answer") + +# remember to change the interactor_id + + +def toia_answer(query, data, engine, interactor_id=41, memory=10, minutes=15): + print("toia_answer") + print(query) + embedding = get_embedding(query, engine='text-embedding-ada-002') + print("API worked") + data['similarities'] = data.ada_search.apply( + lambda x: cosine_similarity(x, embedding)) + res = data.sort_values('similarities', ascending=False) -def toia_answer(query, data, k=1): - embedding = get_embedding(query, engine='text-search-ada-query-001') - data['similarities'] = data.ada_search.apply(lambda x: cosine_similarity(x, embedding)) - res = data.sort_values('similarities', ascending=False).head(k) + ada_similarity_scores = res.similarities.values - ada_similarity_score = res.similarities.values[0] - if ada_similarity_score > 0.29: - return res['answer'].values[0], res['id_video'].values[0], ada_similarity_score + # (removed thresholding. Keep every answer) + if ada_similarity_scores[0] > 0.79: + # take first 10 answers from the chatlog" + # connect to the db + connection = engine.connect() + # retrieve the video IDs already played. The logic here is + # to retrieve the top {memory} last logs from the db and take the ones + # that aren't older than {minutes} + statement = text(f""" + SELECT cl.video_played, + (cl.timestamp - LAG(cl.timestamp, 1) OVER (ORDER BY cl.timestamp)) / 60000 AS time_diff_min + FROM (SELECT * FROM conversations_log WHERE question_asked != "None") cl + WHERE cl.interactor_id = {interactor_id} + ORDER BY cl.timestamp DESC + LIMIT {memory} + ;""") + result_proxy = connection.execute(statement) + result_set = result_proxy.fetchall() + # put them in a df + chatlog = pd.DataFrame(result_set) + # take cumsum of top minute differences -- only problem to highlith here: + # WE DON'T HAVE A NOTION OF WHAT TIME IS NOW + chatlog['cum_time_diff_min'] = chatlog['time_diff_min'].cumsum() + + # define the temp cache of played video IDs -- adding here the notion of NOW: first question of new + # session is going to be put in the cache regardless. Then if time lag is too long from first and last, + # the cache is just that. Then it starts populating. + if chatlog['cum_time_diff_min'][0] >= minutes: + video_cache = chatlog['video_played'].values[0] + else: + video_cache = chatlog[chatlog['cum_time_diff_min'] + < minutes]['video_played'].values + + # store outputs (already sorted by decreasing order of retrieval) + answers = res['answer'].values + videos = res['id_video'].values + scores = ada_similarity_scores + if len(set(video_cache).intersection(set(videos))) > 0: + next_best_idx = next(x for x, vid in enumerate( + videos) if vid not in video_cache) + return answers[next_best_idx], videos[next_best_idx], scores[next_best_idx] + else: + return answers[0], videos[0], scores[0] else: df_noanswers = data[data['type'] == "no-answer"] if df_noanswers.shape[0] > 0: answers = df_noanswers.sample(n=1) - return answers['answer'].values[0], answers['id_video'].values[0], ada_similarity_score + return answers['answer'].values[0], answers['id_video'].values[0], ada_similarity_scores[0] else: - return "You haven't recorded no-answers", "204", "No Content" \ No newline at end of file + return "You haven't recorded no-answers", "204", "No Content" diff --git a/server/translation/toia-translation.json b/server/translation/toia-translation.json new file mode 100644 index 00000000..e8ef3a8d --- /dev/null +++ b/server/translation/toia-translation.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "toia-capstone-2021", + "private_key_id": "c161f9c66a0183737ce5e2ab5f6b59b7df2d33df", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDV8NLTHJLigoKp\nHWWtEVJsP9vAdDwlPQzovF6/W8TKUe9FTNF8XPg48q7vyLpQLb/G3YNbLpH1jjOE\nEKSl+hdUJqG+uUGUv13e5oPsREBMD9FfJ849Bphc5vz4xWj9J7K2G05C0kp2MmtN\n0gNnTJBRjEk2vQwYfuYQWE5ccPbtT4Ga4n0xKzVPR8kId6HSccy9u1eouUOs3oe2\nOUTAvxJxZ721vT6YG3AZfQa6VcPgZGHPIXyKlFU7vlBrHKG+XQi9qi7xLxki+Ae2\nLTdHEwzEphaiqOOEZS9fOj0uzgEpdmDrhM5tbjwkLugCALNbAOYp5vlLB3j38g5P\nobS4rSt3AgMBAAECggEAApMceAKJUmUffTlUcdHaPy5BKHhECKJK9C2sKdTI/sxT\nB+9fbmxqALlyJhv+ATZqIlKkU/Kw0/0xnOtcWxj+5eB1itrAF/7gYNNyW/0+rthz\n4xeS1+r35qPrAHPTvYdlE0orb3EIPCyDXEZ+Pv4aluvZLhz2HWdaWnEA0TdhnJDj\nDATnJZRBSfDxvR3HA3eFQslVcwgTgfXz3PGR8JJ4V9sZMY0rrGpCjRXQf31Hp2LR\nSQ+Gn/AX56fsQ2yf9TdlBDsq9Oj+456amuVNZIP/hvUux4tab0sJqvg/4rBtPJ7x\nC9j3b+le1plBVEq0KHSq/HphIJ394DH1V9vFN48DBQKBgQDwITYrBPdzw5BM5rGF\nsvX1NO273ehNxgt7t9KOrluamTOBFXrnq1vomTFZr9lInbQVwDpM/dvEKhsKPbXn\niAw5tNZD4ARvBr2J9rKVaIxhALohcRvm9UVITEEeP9q9soWTy72BULoOvnh7YYgf\n1ojirBw8I7hj+G3rkF0pZ2GKzQKBgQDkFIQuT9dK0FsMtvrxS779UFO/cWKGKucZ\nNKDqJ2sflN8lqWRAJMK4oO10VbMgpcLfZPJjfrm4pvhfvULVh2FBJm9dpxNLbIhy\nz0BfhK8ZrxzUAx/eMgKa9ODjjeU7qL0rpZF1I/jX4RbhwRTAEEqfgR0mMLc2C4wT\nWznwMGfXUwKBgQCtffFbB42ZcDQzu4GLLY+TCBizYVLTzkDBtEBGoibA/es+Wjyg\nYShYV0ZWnDyKwJY6GNaGhetgQWOj9I4WqC7dCpC8HYBWjzImGb1RQWYbN6FDRCt6\npL7Xy3BI4K2z3OWxsMRMR/0FZgw6aG8nQaNI0jzcHpq8b+NLDNSic3UACQKBgCWY\nqC1GJfQEIm4XH4h09vekrRlqpFX/bna+MSRH+SWMkbgQkyDrrllm1Z2Onudry5Kt\nfjMeaZjhlSGa/hBar5JgtozWhJyzOE7MkQztvztZnUPpe/BRiBJo+UUpV7cc2on+\nUdrgYh2b0fzGqhf614Ixc0+fSiQThTSPnh5UrFlXAoGBALn0FnW6tnTvQOqdUPaL\n9VOD4y0uRD+WL2gqy3czQU/NOzmp7nTrHtVCLrrNZBE3rLhPiAhTgwJMhE3RsahZ\nepUm/QGoRPMNt7yF5/B34+4Jr+4ptK30rJq1qTpcCoUS9DinnF0zvFc5JNRULXIj\nenXtPra/AfhRtPBzeLjQzm0S\n-----END PRIVATE KEY-----\n", + "client_email": "toia-translation@toia-capstone-2021.iam.gserviceaccount.com", + "client_id": "112776380172360861687", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/toia-translation%40toia-capstone-2021.iam.gserviceaccount.com" +}