├── .gitignore
├── AttentionGrabber
├── README.md
├── assets
│ └── webTemplates
│ │ └── BASIC
│ │ ├── css
│ │ ├── bootstrap.min.css
│ │ └── style.css
│ │ └── index.html
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── skill.properties
└── src
│ └── main
│ └── kotlin
│ └── furhatos
│ └── app
│ └── attentionGrabber
│ ├── flow
│ ├── init.kt
│ ├── main
│ │ ├── endReading.kt
│ │ ├── idle.kt
│ │ └── startReading.kt
│ └── parent.kt
│ ├── gestures
│ └── gestures.kt
│ ├── main.kt
│ ├── nlu
│ └── nlu.kt
│ ├── setting
│ ├── interactionParams.kt
│ └── personas.kt
│ └── users.kt
├── ComplimentBot
├── README.md
├── assets
│ └── webTemplates
│ │ └── BASIC
│ │ ├── css
│ │ ├── bootstrap.min.css
│ │ └── style.css
│ │ ├── dist
│ │ └── bundle.js
│ │ └── index.html
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── skill.properties
└── src
│ └── main
│ └── kotlin
│ └── furhatos
│ └── app
│ └── complimentbot
│ ├── flow
│ ├── init.kt
│ ├── main
│ │ ├── endReading.kt
│ │ ├── idle.kt
│ │ └── startReading.kt
│ └── parent.kt
│ ├── gestures
│ └── gestures.kt
│ ├── main.kt
│ ├── nlu
│ └── nlu.kt
│ ├── setting
│ ├── interactionParams.kt
│ └── personas.kt
│ └── users.kt
├── CustomASR
├── .gitignore
├── README.md
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── skill.properties
└── src
│ └── main
│ ├── java
│ └── furhatos
│ │ └── app
│ │ └── customasr
│ │ └── aws
│ │ ├── StreamTranscriptionBehavior.java
│ │ ├── TranscribeApp.java
│ │ └── TranscribeStreamingRetryClient.java
│ └── kotlin
│ └── furhatos
│ └── app
│ └── customasr
│ ├── aws
│ ├── SubscriptionImpl.kt
│ └── TranscriptBehavior.kt
│ ├── com
│ ├── FurhatAudioFeedStreamer.kt
│ ├── FurhatAudioStream.kt
│ └── params.kt
│ ├── events.kt
│ ├── extensions
│ └── listening.kt
│ ├── flow
│ └── init.kt
│ ├── main.kt
│ └── nlu
│ ├── listener.kt
│ └── nlu.kt
├── Dog
├── .gitignore
├── README.md
├── assets
│ └── webTemplates
│ │ └── BASIC
│ │ ├── css
│ │ ├── bootstrap.min.css
│ │ └── style.css
│ │ └── index.html
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── skill.properties
└── src
│ └── main
│ ├── kotlin
│ └── furhatos
│ │ └── app
│ │ └── dog
│ │ ├── dog
│ │ ├── Dog.kt
│ │ └── DogEngagementPolicy.kt
│ │ ├── extensions
│ │ └── flowControlRunnerExtensions.kt
│ │ ├── flow
│ │ ├── general.kt
│ │ ├── parent.kt
│ │ ├── responseHandlers.kt
│ │ ├── showAllGestures.kt
│ │ └── sleepAndWake.kt
│ │ ├── gestures
│ │ ├── backchannelGestures.kt
│ │ ├── barks.kt
│ │ ├── breathIn.kt
│ │ ├── growls.kt
│ │ ├── idleHeadMovements.kt
│ │ ├── lookBackAndAway.kt
│ │ ├── meow.kt
│ │ ├── panting.kt
│ │ ├── randomNeckRoll.kt
│ │ ├── recall.kt
│ │ ├── shake.kt
│ │ ├── smileBack.kt
│ │ ├── sniffing.kt
│ │ ├── squint.kt
│ │ ├── tripleBlink.kt
│ │ ├── wakeUpAndSleep.kt
│ │ ├── whimpering.kt
│ │ └── yawns.kt
│ │ ├── main.kt
│ │ ├── nlu
│ │ └── nlu.kt
│ │ ├── users.kt
│ │ └── utils
│ │ ├── functions.kt
│ │ └── smileBack.kt
│ └── resources
│ ├── gestures
│ ├── Welcome_1.json
│ └── Welcome_2.json
│ └── sounds
│ ├── Medium_dog_growl_vicious_02.pho
│ ├── Medium_dog_growl_vicious_02.wav
│ ├── Medium_dog_growl_vicious_03.wav
│ ├── Medium_dog_growl_vicious_04.pho
│ ├── Medium_dog_growl_vicious_04.wav
│ ├── Medium_large_dog_yawning_02.wav
│ ├── Meow.pho
│ ├── Meow.wav
│ ├── Panting1.wav
│ ├── Small_dog_1_bark.pho
│ ├── Small_dog_1_bark.wav
│ ├── Small_dog_2_barks.pho
│ ├── Small_dog_2_barks.wav
│ ├── Small_dog_3_barks.pho
│ ├── Small_dog_3_barks.wav
│ ├── Small_dog_single_growl_non_aggressive_02.wav
│ ├── Small_dog_single_growl_non_aggressive_04.wav
│ ├── Small_dog_whining_01.pho
│ ├── Small_dog_whining_01.wav
│ ├── Small_dog_whining_02.pho
│ ├── Small_dog_whining_02.wav
│ ├── Small_dog_whining_03.pho
│ ├── Small_dog_whining_03.wav
│ ├── Small_dog_whining_05.pho
│ ├── Small_dog_whining_05.wav
│ ├── Sniffing1.pho
│ ├── Sniffing1.wav
│ ├── Sniffing2.wav
│ ├── Sniffing3.pho
│ ├── Sniffing3.wav
│ ├── d-17a.pho
│ ├── d-17a.wav
│ ├── d-17b.pho
│ └── d-17b.wav
├── FortuneTeller
├── assets
│ └── webTemplates
│ │ └── BASIC
│ │ ├── css
│ │ ├── bootstrap.min.css
│ │ └── style.css
│ │ ├── dist
│ │ └── bundle.js
│ │ └── index.html
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── skill.properties
└── src
│ └── main
│ └── kotlin
│ └── furhatos
│ └── app
│ └── fortuneteller
│ ├── flow
│ ├── init.kt
│ ├── main
│ │ ├── endReading.kt
│ │ ├── idle.kt
│ │ └── startReading.kt
│ └── parent.kt
│ ├── gestures
│ └── gestures.kt
│ ├── main.kt
│ ├── nlu
│ └── nlu.kt
│ ├── setting
│ ├── engagementParams.kt
│ ├── personas.kt
│ └── users.kt
│ └── utils.kt
├── Interviewee
├── .gitignore
├── README.md
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── skill.properties
└── src
│ └── main
│ └── kotlin
│ └── furhatos
│ └── app
│ └── interview
│ ├── flow
│ ├── changeMask.kt
│ ├── generalAnswers.kt
│ ├── init.kt
│ └── main
│ │ └── interview.kt
│ ├── main.kt
│ ├── setting
│ ├── engagementParams.kt
│ └── settings.kt
│ └── util.kt
├── JokeBot
├── .gitignore
├── README.md
├── assets
│ └── webTemplates
│ │ └── BASIC
│ │ ├── css
│ │ ├── bootstrap.min.css
│ │ └── style.css
│ │ └── index.html
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── skill.properties
└── src
│ └── main
│ └── kotlin
│ └── furhatos
│ └── app
│ └── jokebot
│ ├── flow
│ ├── init.kt
│ ├── main
│ │ ├── idle.kt
│ │ ├── jokeSequence.kt
│ │ └── start.kt
│ └── parent.kt
│ ├── jokes
│ ├── gestures.kt
│ ├── jokeHandler.kt
│ └── jokePrompts.kt
│ ├── main.kt
│ ├── nlu
│ └── nlu.kt
│ ├── setting
│ ├── engagementParams.kt
│ ├── personas.kt
│ └── smileBack.kt
│ └── util
│ └── helpers.kt
├── LICENSE
├── OpenAIChat
├── README.md
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── skill.properties
└── src
│ └── main
│ └── kotlin
│ └── furhatos
│ └── app
│ └── openaichat
│ ├── flow
│ ├── chatbot
│ │ ├── chat.kt
│ │ └── openai.kt
│ ├── init.kt
│ ├── main
│ │ ├── greeting.kt
│ │ └── idle.kt
│ └── parent.kt
│ ├── main.kt
│ ├── setting
│ ├── persona.kt
│ └── users.kt
│ └── utils
│ └── utils.kt
├── Quiz
├── .gitignore
├── README.md
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── skill.properties
└── src
│ └── main
│ └── kotlin
│ └── furhatos
│ └── app
│ └── quiz
│ ├── flow
│ ├── init.kt
│ ├── main
│ │ ├── askQuestion.kt
│ │ ├── endGame.kt
│ │ ├── idle.kt
│ │ ├── newGame.kt
│ │ └── start.kt
│ └── parent.kt
│ ├── main.kt
│ ├── nlu.kt
│ ├── questions
│ ├── questionlist.kt
│ └── questions.kt
│ └── setting
│ ├── engagementParams.kt
│ ├── personas.kt
│ └── users.kt
├── README.md
└── demo-skill
├── LICENSE
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── skill.properties
└── src
└── main
└── kotlin
└── furhatos
└── app
└── demo
├── flow
├── actions
│ ├── chat.kt
│ ├── parrot.kt
│ └── presentation.kt
├── autoBehavior
│ ├── attendingLocation.kt
│ ├── attendingUsers.kt
│ ├── autoBehavior.kt
│ └── lookingAround.kt
├── modes
│ ├── active.kt
│ ├── init.kt
│ ├── parent.kt
│ ├── passive.kt
│ └── sleeping.kt
├── partials
│ ├── conversationalHandlers.kt
│ ├── functionalHandlers.kt
│ └── wizardButtons.kt
├── snippets
│ ├── chatSnippets.kt
│ ├── generic.kt
│ ├── greeting.kt
│ ├── locations.kt
│ ├── movies.kt
│ ├── music.kt
│ ├── name.kt
│ ├── personal.kt
│ ├── pets.kt
│ └── smalltalk.kt
└── transitions
│ ├── modelChange.kt
│ ├── personaPass.kt
│ ├── requireUsers.kt
│ ├── return.kt
│ └── verifyWakeup.kt
├── imported
└── quiz
│ ├── nlu.kt
│ ├── questions.kt
│ ├── quiz.kt
│ └── users.kt
├── main.kt
├── nlu
├── conversational.kt
├── entities.kt
└── utility.kt
├── personas
├── personas.kt
└── phrases.kt
├── settings.kt
└── util
├── events.kt
├── extentions.kt
└── util.kt
/.gitignore:
--------------------------------------------------------------------------------
1 | */build
2 | */out
3 | */.gradle
4 | */logs
5 | **/node_modules/
6 | */.idea
7 | *.iml
8 | *dist/
9 | dist/
10 | */dist/
11 | /dist/
12 | */dist
13 | .idea/
--------------------------------------------------------------------------------
/AttentionGrabber/README.md:
--------------------------------------------------------------------------------
1 | # ComplimentBot
2 |
3 | This skill waits for a user to approach, greets them gives them compliment. When it does not get any attention it will get "bored" and eventually fall asleep.
4 |
5 | The skill is using The Anime face and Microsoft Azure voice.
6 |
--------------------------------------------------------------------------------
/AttentionGrabber/assets/webTemplates/BASIC/css/style.css:
--------------------------------------------------------------------------------
1 | *{
2 | box-sizing: border-box;
3 | }
4 |
5 | body{
6 | background: linear-gradient(-60deg, #ff5858 0%, #f09819 100%);
7 | }
8 |
9 | .center{
10 | text-align: center;
11 | }
12 |
13 | p{
14 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
15 | font-size: 30px;
16 | font-weight: 700;
17 | color: #fff;
18 | }
--------------------------------------------------------------------------------
/AttentionGrabber/assets/webTemplates/BASIC/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Furhat Skill
9 |
10 |
11 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/AttentionGrabber/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "org.jetbrains.kotlin.jvm" version "1.8.21"
3 | id 'com.github.johnrengelman.shadow' version '2.0.4'
4 | }
5 |
6 | apply plugin: 'java'
7 | apply plugin: 'kotlin'
8 |
9 | //Defines what version of Java to use.
10 | sourceCompatibility = 1.8
11 |
12 | //Defines how Kotlin should compile.
13 | compileKotlin {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 |
17 | kotlinOptions {
18 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
19 | jvmTarget = "1.8"
20 | apiVersion = "1.8"
21 | languageVersion = "1.8"
22 | }
23 | }
24 |
25 | //Defines how Kotlin should compile when testingTry to keep it the same as compileKotlin.
26 | compileTestKotlin {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 |
30 | kotlinOptions {
31 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
32 | jvmTarget = "1.8"
33 | apiVersion = "1.8"
34 | languageVersion = "1.8"
35 | }
36 | }
37 |
38 | repositories {
39 | mavenLocal()
40 | maven { url "https://s3-eu-west-1.amazonaws.com/furhat-maven/releases"}
41 | jcenter()
42 | }
43 |
44 | dependencies {
45 | implementation 'com.furhatrobotics.furhatos:furhat-commons:2.7.0'
46 | }
47 |
48 | jar {
49 | def lowerCasedName = baseName.toLowerCase()
50 | def normalizedName = lowerCasedName.substring(0,1).toUpperCase() + lowerCasedName.substring(1)
51 | manifest.attributes(
52 | 'Class-Path': configurations.compileClasspath.collect { it.getName() }.join(' '),
53 | 'Main-Class': "furhatos.app.${lowerCasedName}.${normalizedName}Skill"
54 | )
55 | }
56 |
57 | //ShadowJar depends on jar being finished properly.
58 | shadowJar {
59 | manifest {
60 | exclude '**/Log4j2Plugins.dat'
61 | exclude '**/node_modules'
62 | }
63 | from "skill.properties"
64 | from "assets"
65 | extension 'skill'
66 | Properties properties = new Properties()
67 | properties.load(project.file('skill.properties').newDataInputStream())
68 | def version = properties.getProperty('version')
69 | def name = properties.getProperty('name')
70 | archiveName = "${name}_${version}.skill"
71 | }
72 |
--------------------------------------------------------------------------------
/AttentionGrabber/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/AttentionGrabber/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/AttentionGrabber/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-bin.zip
6 |
--------------------------------------------------------------------------------
/AttentionGrabber/skill.properties:
--------------------------------------------------------------------------------
1 | name = attentiongrabber
2 | version = 1.0.0
3 | mainclass = furhatos.app.attentionGrabber.AttentionGrabberSkill
4 | language = en-US
5 | logLevel = INFO
6 | #You may set this to a Furhat version.
7 | requiresVersion = false
8 | #Set to true if this skill should fail if there is no camera
9 | requiresCamera = false
10 | #Set to true if this skill should fail if there is no speaker
11 | requiresSpeaker = false
12 | #Set to true if this skill should fail if there is no microphone
13 | requiresMicrophone = false
14 | #Set to true if this skill should fail if there is no active recognizer
15 | requiresRecognizer = false
--------------------------------------------------------------------------------
/AttentionGrabber/src/main/kotlin/furhatos/app/attentionGrabber/flow/init.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.attentiongrabber.flow
2 |
3 | import furhatos.app.attentiongrabber.flow.main.Idle
4 | import furhatos.app.attentiongrabber.setting.activate
5 | import furhatos.app.attentiongrabber.setting.mainPersona
6 | import furhatos.app.attentiongrabber.setting.maxNumberOfUsers
7 | import furhatos.flow.kotlin.state
8 | import furhatos.flow.kotlin.users
9 |
10 |
11 | val Init = state {
12 | onEntry {
13 | /** Set our default interaction parameters */
14 | users.setSimpleEngagementPolicy(0.5, 1.2, 1.2, 1.7, maxNumberOfUsers)
15 |
16 | /** Set our main character - defined in personas */
17 | activate(mainPersona)
18 |
19 | /** start the interaction */
20 | goto(Idle)
21 | }
22 | }
--------------------------------------------------------------------------------
/AttentionGrabber/src/main/kotlin/furhatos/app/attentionGrabber/flow/main/endReading.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.attentiongrabber.flow.main
2 |
3 | import furhatos.app.attentiongrabber.gestures.FallAsleep
4 | import furhatos.app.attentiongrabber.served
5 | import furhatos.app.attentiongrabber.setting.lookDown
6 | import furhatos.app.attentiongrabber.setting.lookForward
7 | import furhatos.event.EventSystem
8 | import furhatos.event.actions.ActionAttend
9 | import furhatos.event.actions.ActionGaze
10 | import furhatos.flow.kotlin.furhat
11 | import furhatos.flow.kotlin.state
12 | import furhatos.flow.kotlin.users
13 |
14 | val EndReading = state {
15 | onEntry {
16 | furhat.attend(lookForward)
17 | delay(800)
18 | furhat.gesture(FallAsleep, priority = 10)
19 | delay(600)
20 | furhat.ledStrip.solid(java.awt.Color(0, 0, 120))
21 | EventSystem.send(ActionAttend.Builder().location(lookDown).speed(ActionGaze.Speed.XSLOW).buildEvent())
22 | delay(2500)
23 | val unServedUsers = users.list.filter { !it.served }
24 | if (!unServedUsers.isEmpty()) {
25 | println("There are unserved users" + unServedUsers.size)
26 | goto(startReading(unServedUsers.random()))
27 | } else {
28 | goto(Idle)
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/AttentionGrabber/src/main/kotlin/furhatos/app/attentionGrabber/flow/main/idle.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.attentiongrabber.flow.main
2 |
3 | import furhatos.flow.kotlin.*
4 |
5 | val Idle: State = state {
6 |
7 | init {
8 | //furhat.setVoice(Language.ENGLISH_US, Gender.MALE)
9 | //if (users.count > 0) {
10 | //furhat.attend(users.random)
11 | //goto(Start)
12 | //}
13 | if (furhat.isVirtual() && users.hasAny() == false) {
14 | furhat.say("Add a Virtual User to start the interaction. ")
15 | }
16 | }
17 |
18 | onEntry {
19 | //furhat.attendNobody()
20 | }
21 |
22 | onUserEnter {
23 | goto(startReading(it))
24 | }
25 | }
--------------------------------------------------------------------------------
/AttentionGrabber/src/main/kotlin/furhatos/app/attentionGrabber/flow/parent.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.attentiongrabber.flow
2 |
3 | import furhatos.app.attentiongrabber.flow.main.EndReading
4 | import furhatos.flow.kotlin.*
5 |
6 | val Parent: State = state {
7 |
8 | onUserLeave(instant = true) {
9 | if (it == users.current) {
10 | goto(EndReading)
11 | }
12 | /*if (users.count > 0) {
13 | if (it == users.current) {
14 | furhat.attend(users.other)
15 | goto(Start)
16 | } else {
17 | furhat.glance(it)
18 | }
19 | } else {
20 | goto(EndReading)
21 | }*/
22 | }
23 |
24 | onUserEnter(instant = true) {
25 | furhat.glance(it)
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/AttentionGrabber/src/main/kotlin/furhatos/app/attentionGrabber/gestures/gestures.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.attentiongrabber.gestures
2 |
3 | import furhatos.gestures.BasicParams
4 | import furhatos.gestures.defineGesture
5 |
6 | val cSmile = defineGesture("cSmile") {
7 | frame(0.5, persist = true){
8 | BasicParams.BLINK_LEFT to 1.0
9 | BasicParams.BLINK_RIGHT to 1.0
10 | }
11 |
12 | }
13 |
14 | fun rollHead(strength: Double = 1.0, duration: Double = 1.0) =
15 | defineGesture("rollHead") {
16 | frame(0.4, duration) {
17 | BasicParams.NECK_ROLL to strength
18 | }
19 | reset(duration+0.1)
20 | }
21 |
22 | val FallAsleep = defineGesture("FallAsleep") {
23 | frame(0.5, persist = true){
24 | BasicParams.BLINK_LEFT to 1.0
25 | BasicParams.BLINK_RIGHT to 1.0
26 | }
27 |
28 | }
29 |
30 | val MySmile = defineGesture("MySmile") {
31 | frame(0.32, 0.72) {
32 | BasicParams.SMILE_CLOSED to 2.0
33 | }
34 | frame(0.2, 0.72){
35 | BasicParams.BROW_UP_LEFT to 4.0
36 | BasicParams.BROW_UP_RIGHT to 4.0
37 | }
38 | frame(0.16, 0.72){
39 | BasicParams.BLINK_LEFT to 1.0
40 | BasicParams.BLINK_RIGHT to 0.1
41 | }
42 | reset(1.04)
43 | }
44 |
45 | val TripleBlink = defineGesture("TripleBlink") {
46 | frame(0.1, 0.3){
47 | BasicParams.BLINK_LEFT to 1.0
48 | BasicParams.BLINK_RIGHT to 1.0
49 | }
50 | frame(0.3, 0.5){
51 | BasicParams.BLINK_LEFT to 0.1
52 | BasicParams.BLINK_RIGHT to 0.1
53 | }
54 | frame(0.5, 0.7){
55 | BasicParams.BLINK_LEFT to 1.0
56 | BasicParams.BLINK_RIGHT to 1.0
57 | }
58 | frame(0.7, 0.9){
59 | BasicParams.BLINK_LEFT to 0.1
60 | BasicParams.BLINK_RIGHT to 0.1
61 | BasicParams.BROW_UP_LEFT to 2.0
62 | BasicParams.BROW_UP_RIGHT to 2.0
63 | }
64 | frame(0.9, 1.1){
65 | BasicParams.BLINK_LEFT to 1.0
66 | BasicParams.BLINK_RIGHT to 1.0
67 | }
68 | frame(1.1, 1.4){
69 | BasicParams.BLINK_LEFT to 0.1
70 | BasicParams.BLINK_RIGHT to 0.1
71 | }
72 | frame(1.4, 1.5){
73 | BasicParams.BROW_UP_LEFT to 0
74 | BasicParams.BROW_UP_RIGHT to 0
75 | }
76 | reset(1.5)
77 | }
--------------------------------------------------------------------------------
/AttentionGrabber/src/main/kotlin/furhatos/app/attentionGrabber/main.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.attentionGrabber
2 |
3 | import furhatos.app.attentiongrabber.flow.*
4 | import furhatos.skills.Skill
5 | import furhatos.flow.kotlin.*
6 |
7 | class AttentionGrabberSkill : Skill() {
8 | override fun start() {
9 | Flow().run(Init)
10 | }
11 | }
12 |
13 | fun main(args: Array) {
14 | Skill.main(args)
15 | }
16 |
--------------------------------------------------------------------------------
/AttentionGrabber/src/main/kotlin/furhatos/app/attentionGrabber/nlu/nlu.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.attentiongrabber.nlu
--------------------------------------------------------------------------------
/AttentionGrabber/src/main/kotlin/furhatos/app/attentionGrabber/setting/interactionParams.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.attentiongrabber.setting
2 |
3 | import furhatos.records.Location
4 |
5 | val maxNumberOfUsers = 4
6 | val distanceToEngage = 1.0 // not used. We set a more spcecifc shape of the interaction space
7 |
8 |
9 | val lookForward = Location(0.0, 0.0, 1.0)
10 | val lookDown = Location(0.0, -10.0, 1.0)
--------------------------------------------------------------------------------
/AttentionGrabber/src/main/kotlin/furhatos/app/attentionGrabber/setting/personas.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.attentiongrabber.setting
2 |
3 | import furhatos.flow.kotlin.FlowControlRunner
4 | import furhatos.flow.kotlin.furhat
5 | import furhatos.flow.kotlin.voice.PollyNeuralVoice
6 | import furhatos.flow.kotlin.voice.Voice
7 |
8 | class Persona(val name: String, val mask: String = "adult", val face: List, val voice: List)
9 |
10 | fun FlowControlRunner.activate(persona: Persona) {
11 | for (voice in persona.voice) {
12 | if (voice.isAvailable) {
13 | furhat.voice = voice
14 | break
15 | }
16 | }
17 |
18 | for (face in persona.face) {
19 | if (furhat.faces.get(persona.mask)?.contains(face)!!) {
20 | furhat.character = face
21 | break
22 | }
23 | }
24 | }
25 |
26 | val mainPersona = Persona(
27 | name = "Isabel",
28 | face = listOf(
29 | "Isabel",
30 | "Fedora"), // backup if Isabel is not available
31 | voice = listOf(PollyNeuralVoice.Amy())
32 | )
--------------------------------------------------------------------------------
/AttentionGrabber/src/main/kotlin/furhatos/app/attentionGrabber/users.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.attentiongrabber
2 |
3 | import furhatos.flow.kotlin.NullSafeUserDataDelegate
4 | import furhatos.records.User
5 |
6 | var User.served by NullSafeUserDataDelegate { false }
--------------------------------------------------------------------------------
/ComplimentBot/README.md:
--------------------------------------------------------------------------------
1 | # ComplimentBot
2 |
3 | This skill makes the robot wake up and give a compliment to each person that appears.
4 |
5 | It will remember who it has given a compliment to and only gives a person one compliment.
6 |
7 | It is an offshoot of the original fortune teller skill.
--------------------------------------------------------------------------------
/ComplimentBot/assets/webTemplates/BASIC/css/style.css:
--------------------------------------------------------------------------------
1 | *{
2 | box-sizing: border-box;
3 | }
4 |
5 | body{
6 | background: linear-gradient(-60deg, #ff5858 0%, #f09819 100%);
7 | }
8 |
9 | .center{
10 | text-align: center;
11 | }
12 |
13 | p{
14 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
15 | font-size: 30px;
16 | font-weight: 700;
17 | color: #fff;
18 | }
--------------------------------------------------------------------------------
/ComplimentBot/assets/webTemplates/BASIC/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Furhat Skill
9 |
10 |
11 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/ComplimentBot/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "org.jetbrains.kotlin.jvm" version "1.8.21"
3 | id 'com.github.johnrengelman.shadow' version '2.0.4'
4 | }
5 |
6 | apply plugin: 'java'
7 | apply plugin: 'kotlin'
8 |
9 | //Defines what version of Java to use.
10 | sourceCompatibility = 1.8
11 |
12 | //Defines how Kotlin should compile.
13 | compileKotlin {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 |
17 | kotlinOptions {
18 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
19 | jvmTarget = "1.8"
20 | apiVersion = "1.8"
21 | languageVersion = "1.8"
22 | }
23 | }
24 |
25 | //Defines how Kotlin should compile when testingTry to keep it the same as compileKotlin.
26 | compileTestKotlin {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 |
30 | kotlinOptions {
31 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
32 | jvmTarget = "1.8"
33 | apiVersion = "1.8"
34 | languageVersion = "1.8"
35 | }
36 | }
37 |
38 | repositories {
39 | mavenLocal()
40 | maven { url "https://s3-eu-west-1.amazonaws.com/furhat-maven/releases"}
41 | jcenter()
42 | }
43 |
44 | dependencies {
45 | implementation 'com.furhatrobotics.furhatos:furhat-commons:2.7.0'
46 | }
47 |
48 | jar {
49 | def lowerCasedName = baseName.toLowerCase()
50 | def normalizedName = lowerCasedName.substring(0,1).toUpperCase() + lowerCasedName.substring(1)
51 | manifest.attributes(
52 | 'Class-Path': configurations.compileClasspath.collect { it.getName() }.join(' '),
53 | 'Main-Class': "furhatos.app.${lowerCasedName}.${normalizedName}Skill"
54 | )
55 | }
56 |
57 | //ShadowJar depends on jar being finished properly.
58 | shadowJar {
59 | manifest {
60 | exclude '**/Log4j2Plugins.dat'
61 | exclude '**/node_modules'
62 | }
63 | from "skill.properties"
64 | from "assets"
65 | extension 'skill'
66 | Properties properties = new Properties()
67 | properties.load(project.file('skill.properties').newDataInputStream())
68 | def version = properties.getProperty('version')
69 | def name = properties.getProperty('name')
70 | archiveName = "${name}_${version}.skill"
71 | }
72 |
--------------------------------------------------------------------------------
/ComplimentBot/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/ComplimentBot/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/ComplimentBot/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-bin.zip
6 |
--------------------------------------------------------------------------------
/ComplimentBot/skill.properties:
--------------------------------------------------------------------------------
1 | name = ComplimentBot
2 | version = 1.1.1
3 | mainclass = furhatos.app.complimentbot.ComplimentbotSkill
4 | language = en-US
5 | logLevel = INFO
6 | #You may set this to a Furhat version.
7 | requiresVersion = false
8 | #Set to true if this skill should fail if there is no camera
9 | requiresCamera = false
10 | #Set to true if this skill should fail if there is no speaker
11 | requiresSpeaker = false
12 | #Set to true if this skill should fail if there is no microphone
13 | requiresMicrophone = false
14 | #Set to true if this skill should fail if there is no active recognizer
15 | requiresRecognizer = false
--------------------------------------------------------------------------------
/ComplimentBot/src/main/kotlin/furhatos/app/complimentbot/flow/init.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.complimentbot.flow
2 |
3 | import furhatos.app.complimentbot.flow.main.Idle
4 | import furhatos.app.complimentbot.setting.activate
5 | import furhatos.app.complimentbot.setting.mainPersona
6 | import furhatos.app.complimentbot.setting.maxNumberOfUsers
7 | import furhatos.flow.kotlin.state
8 | import furhatos.flow.kotlin.users
9 |
10 |
11 | val Init = state {
12 | onEntry {
13 | /** Set our default interaction parameters */
14 | users.setSimpleEngagementPolicy(0.5, 1.2, 1.2, 1.7, maxNumberOfUsers)
15 |
16 | /** Set our main character - defined in personas */
17 | activate(mainPersona)
18 |
19 | /** start the interaction */
20 | goto(Idle)
21 | }
22 | }
--------------------------------------------------------------------------------
/ComplimentBot/src/main/kotlin/furhatos/app/complimentbot/flow/main/endReading.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.complimentbot.flow.main
2 |
3 | import furhatos.app.complimentbot.flow.served
4 | import furhatos.app.complimentbot.gestures.FallAsleep
5 | import furhatos.app.complimentbot.setting.lookDown
6 | import furhatos.app.complimentbot.setting.lookForward
7 | import furhatos.event.EventSystem
8 | import furhatos.event.actions.ActionAttend
9 | import furhatos.event.actions.ActionGaze
10 | import furhatos.flow.kotlin.furhat
11 | import furhatos.flow.kotlin.state
12 | import furhatos.flow.kotlin.users
13 |
14 | val EndReading = state {
15 | onEntry {
16 | furhat.attend(lookForward)
17 | delay(800)
18 | furhat.gesture(FallAsleep, priority = 10)
19 | delay(600)
20 | furhat.ledStrip.solid(java.awt.Color(0, 0, 120))
21 | EventSystem.send(ActionAttend.Builder().location(lookDown).speed(ActionGaze.Speed.XSLOW).buildEvent())
22 | delay(2500)
23 | val unServedUsers = users.list.filter { !it.served }
24 | if (!unServedUsers.isEmpty()) {
25 | println("There are unserved users" + unServedUsers.size)
26 | goto(startReading(unServedUsers.random()))
27 | } else {
28 | goto(Idle)
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/ComplimentBot/src/main/kotlin/furhatos/app/complimentbot/flow/main/idle.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.complimentbot.flow.main
2 |
3 | import furhatos.flow.kotlin.*
4 |
5 | val Idle: State = state {
6 |
7 | init {
8 | //furhat.setVoice(Language.ENGLISH_US, Gender.MALE)
9 | //if (users.count > 0) {
10 | //furhat.attend(users.random)
11 | //goto(Start)
12 | //}
13 | if (furhat.isVirtual() && users.hasAny() == false) {
14 | furhat.say("Add a Virtual User to start the interaction. ")
15 | }
16 | }
17 |
18 | onEntry {
19 | //furhat.attendNobody()
20 | }
21 |
22 | onUserEnter {
23 | goto(startReading(it))
24 | }
25 | }
--------------------------------------------------------------------------------
/ComplimentBot/src/main/kotlin/furhatos/app/complimentbot/flow/parent.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.complimentbot.flow
2 |
3 | import furhatos.app.complimentbot.flow.main.EndReading
4 | import furhatos.flow.kotlin.*
5 |
6 | val Parent: State = state {
7 |
8 | onUserLeave(instant = true) {
9 | if (it == users.current) {
10 | goto(EndReading)
11 | }
12 | /*if (users.count > 0) {
13 | if (it == users.current) {
14 | furhat.attend(users.other)
15 | goto(Start)
16 | } else {
17 | furhat.glance(it)
18 | }
19 | } else {
20 | goto(EndReading)
21 | }*/
22 | }
23 |
24 | onUserEnter(instant = true) {
25 | furhat.glance(it)
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/ComplimentBot/src/main/kotlin/furhatos/app/complimentbot/gestures/gestures.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.complimentbot.gestures
2 |
3 | import furhatos.gestures.BasicParams
4 | import furhatos.gestures.defineGesture
5 |
6 | val cSmile = defineGesture("cSmile") {
7 | frame(0.5, persist = true){
8 | BasicParams.BLINK_LEFT to 1.0
9 | BasicParams.BLINK_RIGHT to 1.0
10 | }
11 |
12 | }
13 |
14 | fun rollHead(strength: Double = 1.0, duration: Double = 1.0) =
15 | defineGesture("rollHead") {
16 | frame(0.4, duration) {
17 | BasicParams.NECK_ROLL to strength
18 | }
19 | reset(duration+0.1)
20 | }
21 |
22 | val FallAsleep = defineGesture("FallAsleep") {
23 | frame(0.5, persist = true){
24 | BasicParams.BLINK_LEFT to 1.0
25 | BasicParams.BLINK_RIGHT to 1.0
26 | }
27 |
28 | }
29 |
30 | val MySmile = defineGesture("MySmile") {
31 | frame(0.32, 0.72) {
32 | BasicParams.SMILE_CLOSED to 2.0
33 | }
34 | frame(0.2, 0.72){
35 | BasicParams.BROW_UP_LEFT to 4.0
36 | BasicParams.BROW_UP_RIGHT to 4.0
37 | }
38 | frame(0.16, 0.72){
39 | BasicParams.BLINK_LEFT to 1.0
40 | BasicParams.BLINK_RIGHT to 0.1
41 | }
42 | reset(1.04)
43 | }
44 |
45 | val TripleBlink = defineGesture("TripleBlink") {
46 | frame(0.1, 0.3){
47 | BasicParams.BLINK_LEFT to 1.0
48 | BasicParams.BLINK_RIGHT to 1.0
49 | }
50 | frame(0.3, 0.5){
51 | BasicParams.BLINK_LEFT to 0.1
52 | BasicParams.BLINK_RIGHT to 0.1
53 | }
54 | frame(0.5, 0.7){
55 | BasicParams.BLINK_LEFT to 1.0
56 | BasicParams.BLINK_RIGHT to 1.0
57 | }
58 | frame(0.7, 0.9){
59 | BasicParams.BLINK_LEFT to 0.1
60 | BasicParams.BLINK_RIGHT to 0.1
61 | BasicParams.BROW_UP_LEFT to 2.0
62 | BasicParams.BROW_UP_RIGHT to 2.0
63 | }
64 | frame(0.9, 1.1){
65 | BasicParams.BLINK_LEFT to 1.0
66 | BasicParams.BLINK_RIGHT to 1.0
67 | }
68 | frame(1.1, 1.4){
69 | BasicParams.BLINK_LEFT to 0.1
70 | BasicParams.BLINK_RIGHT to 0.1
71 | }
72 | frame(1.4, 1.5){
73 | BasicParams.BROW_UP_LEFT to 0
74 | BasicParams.BROW_UP_RIGHT to 0
75 | }
76 | reset(1.5)
77 | }
--------------------------------------------------------------------------------
/ComplimentBot/src/main/kotlin/furhatos/app/complimentbot/main.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.complimentbot
2 |
3 | import furhatos.app.complimentbot.flow.*
4 | import furhatos.skills.Skill
5 | import furhatos.flow.kotlin.*
6 |
7 | class ComplimentbotSkill : Skill() {
8 | override fun start() {
9 | Flow().run(Init)
10 | }
11 | }
12 |
13 | fun main(args: Array) {
14 | Skill.main(args)
15 | }
16 |
--------------------------------------------------------------------------------
/ComplimentBot/src/main/kotlin/furhatos/app/complimentbot/nlu/nlu.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.complimentbot.nlu
--------------------------------------------------------------------------------
/ComplimentBot/src/main/kotlin/furhatos/app/complimentbot/setting/interactionParams.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.complimentbot.setting
2 |
3 | import furhatos.records.Location
4 |
5 | val maxNumberOfUsers = 4
6 | val distanceToEngage = 1.0 // not used. We set a more spcecifc shape of the interaction space
7 |
8 |
9 | val lookForward = Location(0.0, 0.0, 1.0)
10 | val lookDown = Location(0.0, -10.0, 1.0)
--------------------------------------------------------------------------------
/ComplimentBot/src/main/kotlin/furhatos/app/complimentbot/setting/personas.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.complimentbot.setting
2 |
3 | import furhatos.flow.kotlin.FlowControlRunner
4 | import furhatos.flow.kotlin.furhat
5 | import furhatos.flow.kotlin.voice.PollyNeuralVoice
6 | import furhatos.flow.kotlin.voice.Voice
7 |
8 | class Persona(val name: String, val mask: String = "adult", val face: List, val voice: List)
9 |
10 | fun FlowControlRunner.activate(persona: Persona) {
11 | for (voice in persona.voice) {
12 | if (voice.isAvailable) {
13 | furhat.voice = voice
14 | break
15 | }
16 | }
17 |
18 | for (face in persona.face) {
19 | if (furhat.faces.get(persona.mask)?.contains(face)!!) {
20 | furhat.character = face
21 | break
22 | }
23 | }
24 | }
25 |
26 | val mainPersona = Persona(
27 | name = "Isabel",
28 | face = listOf(
29 | "Isabel",
30 | "Fedora"), // backup if Isabel is not available
31 | voice = listOf(PollyNeuralVoice.Amy())
32 | )
--------------------------------------------------------------------------------
/ComplimentBot/src/main/kotlin/furhatos/app/complimentbot/users.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.complimentbot.flow
2 |
3 | import furhatos.flow.kotlin.NullSafeUserDataDelegate
4 | import furhatos.records.User
5 |
6 | var User.served by NullSafeUserDataDelegate { false }
--------------------------------------------------------------------------------
/CustomASR/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | /out
3 | /build
4 | build/
5 | /.gradle
6 | /logs
7 | /.idea
8 | save_flow.xml
9 | *.iml
10 |
--------------------------------------------------------------------------------
/CustomASR/README.md:
--------------------------------------------------------------------------------
1 | # Skill
2 | Skill that shows how a customASR could be implemented.
3 |
4 | ## Description
5 | This Skill used AWS Transcribe as a customASR.
6 | It sends data to AWS and parses the events sent back for usage in a Skill/State,
7 | additionally it calculates a Loudness metric for usage in the onResponse handlers.
8 |
9 | This example also shows how to use extension functions to make a state look nice and neat.
10 |
11 | The Basic state responds to intents, generic speech and silence.
12 |
13 | ## Usage
14 | Change the API_KEY and API_SECRET in the params object to valid AWS credentials.
--------------------------------------------------------------------------------
/CustomASR/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.jetbrains.kotlin.jvm' version '1.8.21'
3 | id 'com.github.johnrengelman.shadow' version '2.0.4'
4 | }
5 |
6 | apply plugin: 'java'
7 | apply plugin: 'kotlin'
8 |
9 | //Defines what version of Java to use.
10 | sourceCompatibility = 1.8
11 |
12 | //Defines how Kotlin should compile.
13 | compileKotlin {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 |
17 | kotlinOptions {
18 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
19 | jvmTarget = "1.8"
20 | apiVersion = "1.8"
21 | languageVersion = "1.8"
22 | }
23 | }
24 |
25 | //Defines how Kotlin should compile when testingTry to keep it the same as compileKotlin.
26 | compileTestKotlin {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 |
30 | kotlinOptions {
31 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
32 | jvmTarget = "1.8"
33 | apiVersion = "1.8"
34 | languageVersion = "1.8"
35 | }
36 | }
37 |
38 | repositories {
39 | mavenLocal()
40 | mavenCentral()
41 | maven { url "https://s3-eu-west-1.amazonaws.com/furhat-maven/releases"}
42 | maven { url 'https://repo.gradle.org/gradle/libs-releases' }
43 | maven { url { "https://repo1.maven.org/maven2/" } }
44 | }
45 |
46 |
47 | dependencies {
48 | implementation 'com.furhatrobotics.furhatos:furhat-commons:2.7.0'
49 | implementation 'software.amazon.awssdk:transcribe:2.19.14'
50 | implementation 'software.amazon.awssdk:transcribestreaming:2.19.14'
51 | implementation 'software.amazon.awssdk:bom:2.19.14'
52 | implementation 'org.zeromq:jeromq:0.5.3'
53 | }
54 |
55 | jar {
56 | def lowerCasedName = baseName.toLowerCase()
57 | def normalizedName = lowerCasedName.substring(0,1).toUpperCase() + lowerCasedName.substring(1)
58 | manifest.attributes(
59 | 'Class-Path': configurations.compileClasspath.collect { it.getName() }.join(' '),
60 | 'Main-Class': "furhatos.app.${lowerCasedName}.${normalizedName}Skill"
61 | )
62 | }
63 |
64 | //ShadowJar depends on jar being finished properly.
65 | shadowJar {
66 | manifest {
67 | exclude '**/Log4j2Plugins.dat'
68 | exclude '**/node_modules'
69 | }
70 | from "skill.properties"
71 | from "assets"
72 | extension 'skill'
73 | }
74 |
--------------------------------------------------------------------------------
/CustomASR/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/CustomASR/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/CustomASR/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-bin.zip
6 |
--------------------------------------------------------------------------------
/CustomASR/skill.properties:
--------------------------------------------------------------------------------
1 | name = CustomASR
2 | mainclass = furhatos.app.customasr.CustomasrSkill
3 | version = 1.0.0
4 | language = en-US
5 | logLevel = INFO
6 | #You may set this to a Furhat version.
7 | requiresVersion = false
8 | #Set to true if this skill should fail if there is no camera
9 | requiresCamera = false
10 | #Set to true if this skill should fail if there is no speaker
11 | requiresSpeaker = false
12 | #Set to true if this skill should fail if there is no microphone
13 | requiresMicrophone = false
14 | #Set to true if this skill should fail if there is no active recognizer
15 | requiresRecognizer = false
--------------------------------------------------------------------------------
/CustomASR/src/main/java/furhatos/app/customasr/aws/StreamTranscriptionBehavior.java:
--------------------------------------------------------------------------------
1 | // snippet-sourcedescription:[StreamTranscriptionBehavior.java is an interface that you implement for streaming transcription.]
2 | // snippet-keyword:[AWS SDK for Java v2]
3 | //snippet-keyword:[Amazon Transcribe]
4 | // snippet-keyword:[Code Sample]
5 | // snippet-sourcetype:[full-example]
6 | // snippet-sourcedate:[11/06/2020]
7 | // snippet-sourceauthor:[scmacdon - AWS]
8 |
9 | /*
10 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
11 | SPDX-License-Identifier: Apache-2.0
12 | */
13 | // snippet-start:[transcribe.java-streaming-client-behavior]
14 | package furhatos.app.customasr.aws;
15 |
16 | import software.amazon.awssdk.services.transcribestreaming.model.StartStreamTranscriptionResponse;
17 | import software.amazon.awssdk.services.transcribestreaming.model.TranscriptResultStream;
18 |
19 | /**
20 | * Defines how a stream response should be handled.
21 | * You should build a class implementing this interface to define the behavior.
22 | */
23 | public interface StreamTranscriptionBehavior {
24 | /**
25 | * Defines how to respond when encountering an error on the stream transcription.
26 | */
27 | void onError(Throwable e);
28 |
29 | /**
30 | * Defines how to respond to the Transcript result stream.
31 | */
32 | void onStream(TranscriptResultStream e);
33 |
34 | /**
35 | * Defines what to do on initiating a stream connection with the service.
36 | */
37 | void onResponse(StartStreamTranscriptionResponse r);
38 |
39 |
40 | /**
41 | * Defines what to do on stream completion
42 | */
43 | void onComplete();
44 | }
45 | // snippet-end:[transcribe.java-streaming-client-behavior]
--------------------------------------------------------------------------------
/CustomASR/src/main/kotlin/furhatos/app/customasr/aws/TranscriptBehavior.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.customasr.aws
2 |
3 | import furhatos.app.customasr.InterimResult
4 | import furhatos.app.customasr.ListenDone
5 | import furhatos.app.customasr.ListenStarted
6 | import furhatos.event.EventSystem
7 | import furhatos.util.CommonUtils
8 | import software.amazon.awssdk.services.transcribestreaming.model.StartStreamTranscriptionResponse
9 | import software.amazon.awssdk.services.transcribestreaming.model.StartStreamTranscriptionResponseHandler
10 | import software.amazon.awssdk.services.transcribestreaming.model.TranscriptEvent
11 | import software.amazon.awssdk.services.transcribestreaming.model.TranscriptResultStream
12 | import java.io.PrintWriter
13 | import java.io.StringWriter
14 |
15 | private val logger = CommonUtils.getLogger("TranscriptResponseHandler")
16 |
17 | /**
18 | * Handles the events returned by AWS transcribe. Mostly sends events back in the system.
19 | */
20 | fun getTranscriptor(): StartStreamTranscriptionResponseHandler {
21 | return StartStreamTranscriptionResponseHandler.builder()
22 | .onResponse { _: StartStreamTranscriptionResponse ->
23 | EventSystem.send(ListenStarted())
24 | logger.info("=== Received Initial response ===")
25 | }
26 | .onError { e: Throwable ->
27 | logger.warn(e.message)
28 | val sw = StringWriter()
29 | e.printStackTrace(PrintWriter(sw))
30 | logger.warn("Error Occurred: $sw")
31 | EventSystem.send(ListenDone())
32 | }
33 | .onComplete {
34 | EventSystem.send(ListenDone())
35 | logger.info("=== All records stream successfully ===")
36 | }
37 | .subscriber { event: TranscriptResultStream ->
38 | val results = (event as TranscriptEvent).transcript().results()
39 | if (results.size > 0) {
40 | if (results[0].alternatives().size > 0) {
41 | if (results[0].alternatives()[0].transcript().isNotEmpty()) {
42 | val result = results[0]
43 | val message = result.alternatives()[0].transcript()
44 | EventSystem.send(InterimResult(message, result.isPartial))
45 | }
46 | }
47 | }
48 | }
49 | .build()
50 | }
--------------------------------------------------------------------------------
/CustomASR/src/main/kotlin/furhatos/app/customasr/com/FurhatAudioFeedStreamer.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.customasr.com
2 |
3 | import furhatos.util.CommonUtils
4 | import org.zeromq.SocketType
5 | import org.zeromq.ZMQ
6 | import kotlin.concurrent.thread
7 |
8 | /**
9 | * Sends data from the audioFeed to all registered listeners.
10 | */
11 | object FurhatAudioFeedStreamer {
12 |
13 | val logger = CommonUtils.getLogger(FurhatAudioFeedStreamer::class.java)
14 | val context: ZMQ.Context = ZMQ.context(1)
15 | var running = false
16 | private set
17 | var runThread: Thread? = null
18 | val audioListeners = mutableListOf()
19 | var ipaddr = ""
20 |
21 | interface AudioStreamingListener {
22 |
23 | fun audioStreamingStopped()
24 | fun audioStreamingStarted()
25 | fun audioStreamingData(data: ByteArray)
26 | }
27 |
28 | fun addListener(audioListener: AudioStreamingListener) {
29 | audioListeners += audioListener
30 | }
31 |
32 | fun start(ipaddr: String) {
33 | if (ipaddr != FurhatAudioFeedStreamer.ipaddr) {
34 | stop()
35 | FurhatAudioFeedStreamer.ipaddr = ipaddr
36 | } else if (running) {
37 | return
38 | }
39 | val socket = context.socket(SocketType.SUB).apply {
40 | receiveTimeOut = 1000
41 | subscribe(byteArrayOf())
42 | connect("tcp://$ipaddr:3001")
43 | }
44 | running = true
45 | audioListeners.forEach { it.audioStreamingStarted() }
46 | logger.info("Starting FurhatAudioFeedStreamer!")
47 | runThread = thread(start = true) {
48 | while (running) {
49 | val data = socket!!.recv()
50 | if (data != null && data.isNotEmpty()) {
51 | audioListeners.forEach { it.audioStreamingData(data) }
52 | }
53 | }
54 | }
55 | }
56 |
57 | fun stop() {
58 | if (running) {
59 | running = false
60 | runThread?.join()
61 | runThread = null
62 | audioListeners.forEach { it.audioStreamingStopped() }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/CustomASR/src/main/kotlin/furhatos/app/customasr/com/params.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.customasr.com
2 |
3 | object params {
4 | const val endSil = 1000L
5 | const val maxSpeech = 15000L
6 | const val timeout = 5000L
7 | // AWS consumes audioData faster than we feed it, so we need to slow it down
8 | const val microphoneTimeoutInMillis = 50L
9 | const val ROBOT_IP_ADDRESS = "127.0.0.1" // 127.0.0.1 for SDK/Local, or RobotIP(192.168.x.x etc) for running remotely.
10 | const val API_KEY = ""
11 | const val API_SECRET = ""
12 | }
--------------------------------------------------------------------------------
/CustomASR/src/main/kotlin/furhatos/app/customasr/events.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.customasr
2 |
3 | import furhatos.event.Event
4 |
5 | /**
6 | * Events send by [TranscriptBehavior]
7 | */
8 | open class InterimResult(val interimText: String, val isPartial: Boolean): Event()
9 | open class RMSResult(val rms: Double): Event()
10 | open class ListenDone: Event()
11 | open class ListenStarted: Event()
12 |
--------------------------------------------------------------------------------
/CustomASR/src/main/kotlin/furhatos/app/customasr/flow/init.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.customasr.flow
2 |
3 | import furhatos.app.customasr.extensions.customListen
4 | import furhatos.app.customasr.extensions.enableStartAudioStream
5 | import furhatos.app.customasr.extensions.onUserSilence
6 | import furhatos.app.customasr.nlu.*
7 | import furhatos.flow.kotlin.State
8 | import furhatos.flow.kotlin.furhat
9 | import furhatos.flow.kotlin.state
10 |
11 | /**
12 | * The state shows how a CustomASR could be used with custom extension functions.
13 | * Listens to Intents (yes, no), or a generic response [TextAndMetrics]
14 | * When no speech is recognized, the [onUserSilence] is triggered.
15 | */
16 | val Basic: State = state {
17 | init {
18 | furhat.enableStartAudioStream() // Start the stream and listener
19 | parallel(ListenState, false) // Start the state in charge of Listening and NLU.
20 | }
21 |
22 | onButton("Ask") {
23 | furhat.say("Hello there!")
24 | furhat.customListen()
25 | }
26 |
27 | onEvent {
28 | furhat.say("Yes!")
29 | }
30 |
31 | onEvent {
32 | if (it.loudness > 54.0) {
33 | furhat.say("LOUD no!")
34 | } else {
35 | furhat.say("Silent no")
36 | }
37 | }
38 |
39 | onEvent {
40 | furhat.say(it.text)
41 | }
42 |
43 | onUserSilence {
44 | furhat.say("You said nothing!")
45 | }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/CustomASR/src/main/kotlin/furhatos/app/customasr/main.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.customasr
2 |
3 | import furhatos.app.customasr.flow.Basic
4 | import furhatos.flow.kotlin.Flow
5 | import furhatos.skills.Skill
6 |
7 | class CustomasrSkill : Skill() {
8 | override fun start() {
9 | Flow().run(Basic)
10 | }
11 | }
12 |
13 | fun main(args: Array) {
14 | Skill.main(args)
15 | }
16 |
--------------------------------------------------------------------------------
/CustomASR/src/main/kotlin/furhatos/app/customasr/nlu/listener.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.customasr.nlu
2 |
3 | import furhatos.app.customasr.InterimResult
4 | import furhatos.app.customasr.ListenDone
5 | import furhatos.app.customasr.ListenStarted
6 | import furhatos.app.customasr.RMSResult
7 | import furhatos.event.EventSystem
8 | import furhatos.flow.kotlin.state
9 | import furhatos.util.CommonUtils
10 |
11 | private val logger = CommonUtils.getLogger("ASR-EventListener")
12 | /**
13 | * Listens to event send by the [furhatos.app.customasr.com.getTranscriptor]
14 | */
15 | val ListenState = state {
16 | var fullText = ""
17 | var listenEnded = false
18 | var rms = 0.0
19 |
20 | onEvent(instant = true) {
21 | logger.info("A new listen has started, resetting state.")
22 | fullText = ""
23 | listenEnded = false
24 | rms = 0.0
25 | }
26 |
27 | onEvent {
28 | rms = it.rms
29 | }
30 |
31 | onEvent(instant = true) {
32 | if (!it.isPartial) {
33 | fullText += "${it.interimText} "
34 | } else {
35 | logger.info("INTERIM: ${it.interimText}")
36 | }
37 | }
38 |
39 | onEvent(instant = true, cond = {!listenEnded}) {
40 | listenEnded = true
41 | logger.info("Listen done")
42 | var eventSend = false
43 | if (fullText.isEmpty()) {
44 | EventSystem.send(NoSpeechDetected())
45 | } else {
46 | /**
47 | * It would be wise to implement a smarter NLU here.
48 | */
49 | NLUList.forEach { (example, constructor) ->
50 | if(fullText.contains(example, ignoreCase = true)) {
51 | eventSend = true
52 | EventSystem.send(
53 | constructor.invoke(fullText, rms) // Send the specific Intent Event
54 | )
55 | }
56 | }
57 | if (!eventSend) {
58 | EventSystem.send(TextAndMetrics(fullText, rms)) // If no specific intent was sent, send a generic one.
59 | }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/CustomASR/src/main/kotlin/furhatos/app/customasr/nlu/nlu.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.customasr.nlu
2 |
3 | import furhatos.event.Event
4 |
5 | /**
6 | * Base Intent event
7 | */
8 | open class TextAndMetrics(
9 | val text: String,
10 | val loudness: Double
11 | ) : Event()
12 |
13 | class Yes(t: String, l: Double): TextAndMetrics(t, l) // An Intent
14 | class No(t: String, l: Double): TextAndMetrics(t, l) // An Intent
15 |
16 | open class NoSpeechDetected: Event() // No Speech
17 |
18 | val NLUList = mapOf TextAndMetrics>( // Maps words to Intents
19 | "Yes " to { text, loudness -> Yes(text, loudness) },
20 | "No " to { text, loudness -> No(text, loudness) }
21 | )
22 |
--------------------------------------------------------------------------------
/Dog/.gitignore:
--------------------------------------------------------------------------------
1 | logs/*
2 | node_modules/
3 | build/
4 | distr/
5 | out/
6 | .gradle/
7 | .idea/
8 | *.iml
9 | /config.properties
10 |
--------------------------------------------------------------------------------
/Dog/README.md:
--------------------------------------------------------------------------------
1 | # Dog
2 | Showcase of a dog character called Lucky.
3 |
4 | ## Description
5 | Who's a good boy? Engage with Lucky the dog and make it more excited, but eventually it will grow tired. A skill for non-verbal (not counting barks) communication!
6 |
7 | ## Usage
8 | Max number of users: 4
9 | Requirements: Dog mask
--------------------------------------------------------------------------------
/Dog/assets/webTemplates/BASIC/css/style.css:
--------------------------------------------------------------------------------
1 | *{
2 | box-sizing: border-box;
3 | }
4 |
5 | body{
6 | background: linear-gradient(-60deg, #ff5858 0%, #f09819 100%);
7 | }
8 |
9 | .center{
10 | text-align: center;
11 | }
12 |
13 | p{
14 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
15 | font-size: 30px;
16 | font-weight: 700;
17 | color: #fff;
18 | }
--------------------------------------------------------------------------------
/Dog/assets/webTemplates/BASIC/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Furhat Skill
9 |
10 |
11 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Dog/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "org.jetbrains.kotlin.jvm" version "1.8.21"
3 | id 'com.github.johnrengelman.shadow' version '2.0.4'
4 | }
5 |
6 | apply plugin: 'java'
7 | apply plugin: 'kotlin'
8 |
9 | //Defines what version of Java to use.
10 | sourceCompatibility = 1.8
11 |
12 | //Defines how Kotlin should compile.
13 | compileKotlin {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 |
17 | kotlinOptions {
18 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
19 | jvmTarget = "1.8"
20 | apiVersion = "1.8"
21 | languageVersion = "1.8"
22 | }
23 | }
24 |
25 | //Defines how Kotlin should compile when testingTry to keep it the same as compileKotlin.
26 | compileTestKotlin {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 |
30 | kotlinOptions {
31 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
32 | jvmTarget = "1.8"
33 | apiVersion = "1.8"
34 | languageVersion = "1.8"
35 | }
36 | }
37 |
38 | repositories {
39 | mavenLocal()
40 | mavenCentral()
41 | maven { url "https://s3-eu-west-1.amazonaws.com/furhat-maven/releases"}
42 | maven { url 'https://repo.gradle.org/gradle/libs-releases' }
43 | }
44 |
45 | dependencies {
46 | implementation 'com.furhatrobotics.furhatos:furhat-commons:2.7.0'
47 | }
48 |
49 | jar {
50 | def lowerCasedName = baseName.toLowerCase()
51 | def normalizedName = lowerCasedName.substring(0,1).toUpperCase() + lowerCasedName.substring(1)
52 | manifest.attributes(
53 | 'Class-Path': configurations.compileClasspath.collect { it.getName() }.join(' '),
54 | 'Main-Class': "furhatos.app.${lowerCasedName}.${normalizedName}Skill"
55 | )
56 | }
57 |
58 | //ShadowJar depends on jar being finished properly.
59 | shadowJar {
60 | manifest {
61 | exclude '**/Log4j2Plugins.dat'
62 | exclude '**/node_modules'
63 | }
64 | from "skill.properties"
65 | from "assets"
66 | extension 'skill'
67 | Properties properties = new Properties()
68 | properties.load(project.file('skill.properties').newDataInputStream())
69 | def version = properties.getProperty('version')
70 | def name = properties.getProperty('name')
71 | archiveName = "${name}_${version}.skill"
72 | }
73 |
--------------------------------------------------------------------------------
/Dog/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Dog/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-bin.zip
6 |
--------------------------------------------------------------------------------
/Dog/skill.properties:
--------------------------------------------------------------------------------
1 | name = Dog
2 | mainclass = furhatos.app.dog.DogSkill
3 | version = 1.0.1
4 | language = en-US
5 | logLevel = INFO
6 | #You may set this to a Furhat version.
7 | requiresVersion = false
8 | #Set to true if this skill should fail if there is no camera
9 | requiresCamera = false
10 | #Set to true if this skill should fail if there is no speaker
11 | requiresSpeaker = false
12 | #Set to true if this skill should fail if there is no microphone
13 | requiresMicrophone = false
14 | #Set to true if this skill should fail if there is no active recognizer
15 | requiresRecognizer = false
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/flow/parent.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.flow
2 |
3 |
4 | import furhatos.flow.kotlin.*
5 |
6 |
7 | val Parent: State = state {
8 | onButton("SLEEP", color = Color.Blue) {
9 | goto(Sleep)
10 | }
11 | onButton("WAKE", color = Color.Blue) {
12 | goto(WakeUp)
13 | }
14 | onButton("SHOW ALL GESTURES", color = Color.Blue) {
15 | goto(ShowAllGestures)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/flow/showAllGestures.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.flow
2 |
3 | import furhatos.app.dog.gestures.*
4 | import furhatos.flow.kotlin.State
5 | import furhatos.flow.kotlin.furhat
6 | import furhatos.flow.kotlin.state
7 |
8 | val ShowAllGestures: State = state(Parent) {
9 | onEntry {
10 | furhat.gesture(panting1, async = false)
11 | delay(500)
12 | furhat.gesture(growlPositive2, async = false)
13 | delay(500)
14 | furhat.gesture(sniffing3, async = false)
15 | delay(500)
16 | furhat.gesture(growlPositive4, async = false)
17 | delay(500)
18 | furhat.gesture(sniffing3, async = false)
19 | delay(500)
20 | furhat.gesture(yawn1, async = false)
21 | delay(500)
22 | furhat.gesture(bark1, async = false)
23 | delay(500)
24 | furhat.gesture(whimpering3, async = false)
25 | delay(500)
26 | furhat.gesture(growl2, async = false)
27 | delay(500)
28 | furhat.gesture(whimpering2, async = false)
29 | delay(500)
30 | furhat.gesture(bark3, async = false)
31 | delay(500)
32 | furhat.gesture(shake1, async = false)
33 | delay(500)
34 | furhat.gesture(sniffing1, async = false)
35 | delay(500)
36 | furhat.gesture(whimpering5, async = false)
37 | delay(500)
38 | furhat.gesture(growl4, async = false)
39 | delay(500)
40 | furhat.gesture(meow, async = false)
41 | delay(500)
42 | furhat.gesture(bark2, async = false)
43 | delay(500)
44 | furhat.gesture(whimpering1, async = false)
45 | delay(500)
46 |
47 | goto(Main)
48 | }
49 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/flow/sleepAndWake.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.flow
2 |
3 | import furhatos.app.dog.gestures.shake1
4 | import furhatos.app.dog.gestures.yawn1
5 | import furhatos.flow.kotlin.State
6 | import furhatos.flow.kotlin.furhat
7 | import furhatos.flow.kotlin.state
8 | import gestures.FallAsleep
9 | import gestures.WakeUpWithHeadShake
10 |
11 | val Sleep: State = state(Parent) {
12 | onExit {
13 | furhat.gesture(WakeUpWithHeadShake, priority=10)
14 | }
15 | onEntry {
16 | furhat.gesture(FallAsleep, priority=10)
17 | }
18 | }
19 | val WakeUp: State = state(Parent) {
20 | onEntry {
21 | delay(2300)
22 | furhat.gesture(shake1, async = false)
23 | delay(1000)
24 | furhat.gesture(yawn1, async = false)
25 | }
26 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/barks.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.gestures
2 |
3 | import furhatos.app.dog.utils._defineGesture
4 | import furhatos.app.dog.utils.getAudioURL
5 | import furhatos.gestures.BasicParams
6 |
7 | val bark1 = _defineGesture("bark1", frameTimes = listOf(0.1), audioURL = getAudioURL("Small_dog_1_bark.wav")) {
8 | // This empty frame needs to be here for the audio to not play twice
9 | frame(0.1) { }
10 |
11 | frame(0.15) {
12 | BasicParams.SMILE_OPEN to 0.5
13 | BasicParams.NECK_TILT to -14
14 | }
15 |
16 | frame(0.30) {
17 | BasicParams.NECK_TILT to -0
18 | }
19 |
20 | reset(0.4)
21 | }
22 |
23 | val bark2 = _defineGesture("bark2", frameTimes = listOf(0.0), audioURL = getAudioURL("Small_dog_2_barks.wav")) {
24 | // This empty frame needs to be here for the audio to not play twice
25 | frame(0.0) { }
26 |
27 | frame(0.15, 0.45) {
28 | BasicParams.SMILE_OPEN to 0.5
29 | BasicParams.NECK_TILT to -14
30 | }
31 |
32 | frame(0.3, 0.6) {
33 | BasicParams.NECK_TILT to 6
34 | }
35 |
36 | reset(0.7)
37 | }
38 |
39 | val bark3 = _defineGesture("bark3", frameTimes = listOf(0.0), audioURL = getAudioURL("Small_dog_3_barks.wav")) {
40 | // This empty frame needs to be here for the audio to not play twice
41 | frame(0.0) { }
42 |
43 | frame(0.15, 0.45, 0.8) {
44 | BasicParams.SMILE_OPEN to 0.5
45 | BasicParams.NECK_TILT to -14
46 | }
47 |
48 | frame(0.3, 0.65, 0.95) {
49 | BasicParams.NECK_TILT to -6
50 | }
51 |
52 | reset(1.0)
53 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/breathIn.kt:
--------------------------------------------------------------------------------
1 | package gestures
2 |
3 | import furhatos.gestures.defineGesture
4 | import furhatos.gestures.BasicParams
5 |
6 | /**
7 | * Take a breath right before Speaking.
8 | * Leave about 1 second before beginning to speak. This method would benefit from slower motor movements
9 | */
10 | val BreathIn = defineGesture("BreathIn") {
11 | frame(0.35){
12 | BasicParams.PHONE_AAH to 0.0
13 | BasicParams.NECK_TILT to -14.0
14 | }
15 | frame(0.7){
16 | BasicParams.PHONE_AAH to 0.4
17 | BasicParams.NECK_TILT to -14.0
18 | }
19 | frame(1.4){
20 | BasicParams.PHONE_AAH to 0.0
21 | }
22 | frame(5.8){
23 | BasicParams.NECK_TILT to 5.0
24 | BasicParams.EYE_SQUINT_LEFT to 0.3
25 | BasicParams.EYE_SQUINT_RIGHT to 0.3
26 | }
27 | reset(6.2)
28 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/growls.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.gestures
2 |
3 | import furhatos.app.dog.utils._defineGesture
4 | import furhatos.app.dog.utils.getAudioURL
5 | import furhatos.gestures.BasicParams
6 |
7 | val growlPositive2 = _defineGesture("growlPositive2", frameTimes = listOf(0.2), audioURL = getAudioURL("Small_dog_single_growl_non_aggressive_02.wav")) {
8 | // This empty frame needs to be here for the audio to not play twice
9 | frame(1.0) { }
10 |
11 | frame(0.5, 1.5) {
12 | BasicParams.NECK_ROLL to 10
13 | BasicParams.SMILE_OPEN to 0.8
14 | }
15 |
16 | reset(2.0)
17 | }
18 | val growlPositive4 = _defineGesture("growlPositive4", frameTimes = listOf(0.2), audioURL = getAudioURL("Small_dog_single_growl_non_aggressive_04.wav")) {
19 | // This empty frame needs to be here for the audio to not play twice
20 | frame(1.0) { }
21 |
22 | frame(0.5, 1.5) {
23 | BasicParams.NECK_ROLL to -10
24 | BasicParams.SMILE_OPEN to 0.8
25 | }
26 |
27 | reset(2.0)
28 | }
29 |
30 |
31 | val growl2 = _defineGesture("growl2", frameTimes = listOf(0.2), audioURL = getAudioURL("Medium_dog_growl_vicious_02.wav")) {
32 | // This empty frame needs to be here for the audio to not play twice
33 | frame(0.2) { }
34 |
35 | frame(0.5, 1.5) {
36 | BasicParams.EXPR_ANGER to 1.0
37 | }
38 |
39 | reset(2.0)
40 | }
41 |
42 | val growl4 = _defineGesture("growl4", frameTimes = listOf(1.5), audioURL = getAudioURL("Medium_dog_growl_vicious_04.wav")) {
43 | // This empty frame needs to be here for the audio to not play twice
44 | frame(1.5) { }
45 |
46 | frame(0.5, 3.0) {
47 | BasicParams.EXPR_ANGER to 1.0
48 | }
49 |
50 | reset(4.0)
51 | }
52 |
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/idleHeadMovements.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.gestures
2 |
3 | import furhatos.gestures.BasicParams
4 | import furhatos.gestures.defineGesture
5 |
6 | // Randomizes if Furhat is looking left/right and up/down
7 | fun getRandomDirection(): Double {
8 | return if (Math.random() < 0.5)
9 | -1.0 // negative
10 | else
11 | +1.0 // positive
12 | }
13 |
14 | // Adds variation to the amplitude of the random head movements and sets the range
15 | fun getScaleParameter(): Double {
16 | return getRandomDirection() * (Math.random() + 0.55)
17 | }
18 |
19 | fun idleHeadMovements(strength: Double = 1.0, duration: Double = 1.0, amplitude: Double = 5.0, gazeAway: Boolean = false) =
20 | defineGesture("headMove", strength = strength, duration = duration) {
21 | frame(1.5, 8.5) {
22 | // This regulates how long the position will be held and how fast Furhat gets there. Multiplied with duration.
23 | BasicParams.NECK_TILT to amplitude * getScaleParameter()
24 | BasicParams.NECK_ROLL to amplitude * getScaleParameter()
25 | BasicParams.NECK_PAN to amplitude * getScaleParameter()
26 | // the direction (tilt/roll/pan) of the position shift is completely random. Does not affect attention
27 | }
28 | reset(10.0)
29 | }
30 |
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/lookBackAndAway.kt:
--------------------------------------------------------------------------------
1 | package gestures
2 |
3 | import furhatos.gestures.BasicParams
4 | import furhatos.gestures.defineGesture
5 |
6 | //Look Away. Must be followed by LookBack
7 | fun LookAway() = defineGesture {
8 | frame(0.32, 0.72, persist = true) {
9 | var direction = listOf(-1, 1).shuffled().first()
10 | if (listOf(false, true).shuffled().first()) {
11 | BasicParams.NECK_PAN to -9*direction
12 | BasicParams.GAZE_TILT to -40*direction
13 | BasicParams.NECK_TILT to -6
14 | BasicParams.GAZE_PAN to -16
15 | } else {
16 | //BasicParams.NECK_PAN to -4
17 | BasicParams.GAZE_TILT to -30*direction
18 | BasicParams.GAZE_PAN to -8
19 | }
20 | }
21 | }
22 | //Reverse a LookAway
23 | val LookBack = defineGesture {
24 | frame(0.25, 0.3) {
25 | BasicParams.NECK_PAN to 0
26 | BasicParams.GAZE_TILT to 0
27 | BasicParams.NECK_TILT to 0
28 | BasicParams.GAZE_PAN to 0
29 | }
30 | reset(0.3)
31 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/meow.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.gestures
2 |
3 | import furhatos.app.dog.utils._defineGesture
4 | import furhatos.app.dog.utils.getAudioURL
5 | import furhatos.gestures.BasicParams
6 |
7 | val meow = _defineGesture("meow", frameTimes = listOf(0.2), audioURL = getAudioURL("Meow.wav")) {
8 | // This empty frame needs to be here for the audio to not play twice
9 | frame(1.0) { }
10 |
11 | frame(0.5, 1.5) {
12 | BasicParams.EXPR_ANGER to 0.0
13 | }
14 | frame(1.3, 2.7) {
15 | BasicParams.NECK_ROLL to 0
16 | BasicParams.BROW_UP_RIGHT to 0.0
17 | BasicParams.BROW_UP_LEFT to 0.0
18 | }
19 | frame(1.5, 2.5) {
20 | BasicParams.NECK_ROLL to 11
21 | BasicParams.BROW_UP_RIGHT to 1.0
22 | BasicParams.BROW_UP_LEFT to 1.0
23 | BasicParams.SMILE_CLOSED to 0.0
24 | BasicParams.SMILE_OPEN to 0.0
25 | }
26 | frame(2.8, 3.5) {
27 | BasicParams.SMILE_CLOSED to 1.0
28 | BasicParams.SMILE_OPEN to 0.3
29 | }
30 |
31 | reset(3.9)
32 | }
33 |
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/panting.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.gestures
2 |
3 | import furhatos.app.dog.utils._defineGesture
4 | import furhatos.app.dog.utils.getAudioURL
5 | import furhatos.gestures.BasicParams
6 |
7 | val panting1 = _defineGesture("panting1", frameTimes = listOf(0.2), audioURL = getAudioURL("Panting1.wav")) {
8 |
9 | // This empty frame needs to be here for the audio to not play twice
10 | frame(0.2) { }
11 |
12 | frame(0.3, 0.61, 0.92, 1.23, 1.54, 1.85, 2.16, 2.47) {
13 |
14 | BasicParams.PHONE_EE to 0.9
15 | BasicParams.PHONE_AAH to 0.5
16 | }
17 | frame(0.46, 0.77, 1.08, 1.39, 1.70, 2.0, 2.31, 2.7) {
18 | BasicParams.PHONE_EE to 0.5
19 | BasicParams.PHONE_AAH to 0.2
20 | }
21 |
22 | reset(3.0)
23 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/randomNeckRoll.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.gestures
2 |
3 | import furhatos.gestures.BasicParams
4 | import furhatos.gestures.defineGesture
5 | import kotlin.random.Random
6 |
7 | /**
8 | * Roll the head to a random position
9 | */
10 | val RandomNeckRoll = defineGesture("RandomNeckRoll") {
11 | frame(0.7, 4.3){
12 | BasicParams.NECK_ROLL to Random.nextDouble(-12.0, 12.0)
13 | }
14 | reset(5.0)
15 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/recall.kt:
--------------------------------------------------------------------------------
1 | package gestures
2 |
3 | import furhatos.gestures.defineGesture
4 | import furhatos.gestures.BasicParams
5 |
6 | /**
7 | * Recall a memory
8 | * @param durationMillis The time the expression should last
9 | */
10 | fun Recall(durationMillis: Int = 2000) = defineGesture("Recall") {
11 | val durationSeconds: Double = durationMillis.toDouble()/1000
12 | frame(0.15,durationSeconds-0.15) {
13 | BasicParams.NECK_TILT to -7
14 | BasicParams.GAZE_TILT to -30
15 | BasicParams.NECK_PAN to -7
16 | BasicParams.GAZE_PAN to -22
17 | }
18 | reset(durationSeconds)
19 | }
20 |
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/smileBack.kt:
--------------------------------------------------------------------------------
1 | package gestures
2 |
3 | import furhatos.gestures.defineGesture
4 | import furhatos.gestures.BasicParams
5 |
6 | /**
7 | * Gestures for smileBack.kt
8 | */
9 | //Big Smile
10 | val indefiniteBigSmile = defineGesture {
11 | frame(0.32, 0.64, persist = true) {
12 | BasicParams.BROW_UP_LEFT to 1.0
13 | BasicParams.BROW_UP_RIGHT to 1.0
14 | BasicParams.SMILE_OPEN to 0.5
15 | BasicParams.SMILE_CLOSED to 0.7
16 | }
17 | }
18 |
19 | //Small smile
20 | val indefiniteSmile = defineGesture {
21 | frame(0.32, 0.72, persist = true) {
22 | BasicParams.SMILE_CLOSED to 0.5
23 | }
24 | frame(0.2, 0.72){
25 | BasicParams.BROW_UP_LEFT to 1.0
26 | BasicParams.BROW_UP_RIGHT to 1.0
27 | }
28 | frame(0.16, 0.72){
29 | BasicParams.BLINK_LEFT to 0.1
30 | BasicParams.BLINK_RIGHT to 0.1
31 | }
32 | }
33 |
34 | //No more smiling
35 | val stopSmile = defineGesture {
36 | frame(0.32, 0.64) {
37 | BasicParams.BROW_UP_LEFT to 0.0
38 | BasicParams.BROW_UP_RIGHT to 0.0
39 | BasicParams.SMILE_OPEN to 0.0
40 | BasicParams.SMILE_CLOSED to 0.0
41 | }
42 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/sniffing.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.gestures
2 |
3 | import furhatos.app.dog.utils._defineGesture
4 | import furhatos.app.dog.utils.getAudioURL
5 | import furhatos.gestures.BasicParams
6 |
7 | val sniffing1 = _defineGesture("sniffing1", frameTimes = listOf(0.2), audioURL = getAudioURL("Sniffing1.wav")) {
8 | // This empty frame needs to be here for the audio to not play twice
9 | frame(0.2) { }
10 |
11 | frame(1.0, 3.0, 5.0) {
12 | BasicParams.BROW_DOWN_LEFT to 0.5
13 | BasicParams.BROW_DOWN_RIGHT to 0.5
14 | }
15 | frame(2.0, 4.0, 6.2) {
16 | BasicParams.BROW_DOWN_LEFT to 0.0
17 | BasicParams.BROW_DOWN_RIGHT to 0.0
18 | }
19 |
20 | reset(6.5)
21 | }
22 | val sniffing3 = _defineGesture("sniffing3", frameTimes = listOf(0.2), audioURL = getAudioURL("Sniffing3.wav")) {
23 | // This empty frame needs to be here for the audio to not play twice
24 | frame(0.2) { }
25 |
26 | frame(0.4, 1.7) {
27 | BasicParams.BROW_DOWN_LEFT to 0.5
28 | BasicParams.BROW_DOWN_RIGHT to 0.5
29 | }
30 |
31 | reset(1.7)
32 | }
33 | //
34 | //val sniffing2 = _defineGesture("sniffing2", frameTimes = listOf(1.5), audioURL = getAudioURL("Sniffing2.wav")) {
35 | // // This empty frame needs to be here for the audio to not play twice
36 | // frame(1.5) { }
37 | //
38 | // frame(0.4, 0.8, 1.2, 1.4, 1.8) {
39 | // BasicParams.PHONE_B_M_P to 0.0
40 | // }
41 | // frame(0.6, 1.0, 1.4, 1.6) {
42 | // BasicParams.PHONE_B_M_P to 1.0
43 | // }
44 | //
45 | // reset(6.5)
46 | //}
47 |
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/squint.kt:
--------------------------------------------------------------------------------
1 | package gestures
2 |
3 | import furhatos.gestures.BasicParams
4 | import furhatos.gestures.defineGesture
5 |
6 | /**
7 | * Squint
8 | */
9 | fun Squint(strength: Double = 1.0, duration: Double = 1.0) = defineGesture("Squint", strength, duration) {
10 | frame(0.2, duration-0.2){
11 | BasicParams.EYE_SQUINT_LEFT to strength
12 | BasicParams.EYE_SQUINT_RIGHT to strength
13 | }
14 | reset(duration)
15 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/tripleBlink.kt:
--------------------------------------------------------------------------------
1 | package gestures
2 |
3 | import furhatos.gestures.BasicParams
4 | import furhatos.gestures.defineGesture
5 |
6 | /**
7 | * Blink three times
8 | */
9 | val TripleBlink = defineGesture("TripleBlink") {
10 | frame(0.1, 0.3){
11 | BasicParams.BLINK_LEFT to 1.0
12 | BasicParams.BLINK_RIGHT to 1.0
13 | }
14 | frame(0.3, 0.5){
15 | BasicParams.BLINK_LEFT to 0.1
16 | BasicParams.BLINK_RIGHT to 0.1
17 | }
18 | frame(0.5, 0.7){
19 | BasicParams.BLINK_LEFT to 1.0
20 | BasicParams.BLINK_RIGHT to 1.0
21 | }
22 | frame(0.7, 0.9){
23 | BasicParams.BLINK_LEFT to 0.1
24 | BasicParams.BLINK_RIGHT to 0.1
25 | BasicParams.BROW_UP_LEFT to 2.0
26 | BasicParams.BROW_UP_RIGHT to 2.0
27 | }
28 | frame(0.9, 1.1){
29 | BasicParams.BLINK_LEFT to 1.0
30 | BasicParams.BLINK_RIGHT to 1.0
31 | }
32 | frame(1.1, 1.4){
33 | BasicParams.BLINK_LEFT to 0.1
34 | BasicParams.BLINK_RIGHT to 0.1
35 | }
36 | frame(1.4, 1.5){
37 | BasicParams.BROW_UP_LEFT to 0
38 | BasicParams.BROW_UP_RIGHT to 0
39 | }
40 | reset(1.5)
41 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/whimpering.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.gestures
2 |
3 | import furhatos.app.dog.utils._defineGesture
4 | import furhatos.app.dog.utils.getAudioURL
5 | import furhatos.gestures.BasicParams
6 |
7 | val whimpering1 = _defineGesture("whimpering1", frameTimes = listOf(0.0), audioURL = getAudioURL("Small_dog_whining_01.wav")) {
8 | // This empty frame needs to be here for the audio to not play twice
9 | frame(0.2, 1.5) {
10 | BasicParams.NECK_ROLL to 12
11 | BasicParams.EXPR_DISGUST to 0.5
12 | }
13 | reset(2.5)
14 | }
15 |
16 | val whimpering2 = _defineGesture("whimpering2", frameTimes = listOf(0.0), audioURL = getAudioURL("Small_dog_whining_02.wav")) {
17 | // This empty frame needs to be here for the audio to not play twice
18 | frame(0.0) { }
19 | frame(0.2, 1.5) {
20 | BasicParams.NECK_ROLL to -12
21 | BasicParams.EXPR_DISGUST to 0.5
22 | }
23 |
24 | reset(2.5)
25 | }
26 |
27 | val whimpering3 = _defineGesture("whimpering3", frameTimes = listOf(0.0), audioURL = getAudioURL("Small_dog_whining_03.wav")) {
28 | // This empty frame needs to be here for the audio to not play twice
29 | frame(0.0) { }
30 | frame(0.2, 1.5) {
31 | BasicParams.NECK_ROLL to 12
32 | BasicParams.EXPR_DISGUST to 0.5
33 | }
34 | reset(2.5)
35 | }
36 |
37 | val whimpering5 = _defineGesture("whimpering5", frameTimes = listOf(0.0), audioURL = getAudioURL("Small_dog_whining_05.wav")) {
38 | // This empty frame needs to be here for the audio to not play twice
39 | frame(0.2, 1.5) {
40 | BasicParams.NECK_ROLL to -12
41 | BasicParams.EXPR_DISGUST to 0.5
42 | }
43 | reset(2.5)
44 | }
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/gestures/yawns.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.gestures
2 |
3 | import furhatos.app.dog.utils._defineGesture
4 | import furhatos.app.dog.utils.getAudioURL
5 | import furhatos.gestures.BasicParams
6 |
7 | val yawn1 = _defineGesture("yawn1", frameTimes = listOf(0.2), audioURL = getAudioURL("Medium_large_dog_yawning_02.wav")) {
8 | // This empty frame needs to be here for the audio to not play twice
9 | frame(1.0) { }
10 |
11 | frame(0.2, 1.5) {
12 | BasicParams.PHONE_AAH to 1.0
13 | BasicParams.NECK_TILT to -15.0
14 | BasicParams.NECK_ROLL to 8.0
15 | BasicParams.GAZE_PAN to -18.0
16 | }
17 |
18 | reset(2.0)
19 | }
20 |
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/main.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog
2 |
3 | import furhatos.app.dog.flow.*
4 | import furhatos.skills.Skill
5 | import furhatos.flow.kotlin.*
6 |
7 | class DogSkill : Skill() {
8 | override fun start() {
9 | Flow().run(Main)
10 | }
11 | }
12 |
13 | fun main(args: Array) {
14 | Skill.main(args)
15 | }
16 |
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/users.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.flow
2 |
3 |
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/utils/functions.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.utils
2 |
3 | import furhatos.app.dog.DogSkill
4 | import furhatos.app.dog.flow.runningFromIntelliJ
5 | import furhatos.gestures.*
6 | import furhatos.records.Pixel
7 | import furhatos.records.Record
8 | import java.io.BufferedReader
9 | import java.io.File
10 | import java.io.InputStreamReader
11 |
12 | var isVirtual = false
13 |
14 | // Random double in given range
15 | fun getRandomInRange(startInterval: Double = 18.0, interval: Double = 3.0) = startInterval + Math.random() * interval
16 |
17 | fun getResourceGesture(filePath: String): Gesture {
18 | val resource = DogSkill::class.java.getResourceAsStream(filePath)
19 | return if (resource != null) {
20 | Record.fromJSON(BufferedReader(InputStreamReader(resource)).readText()) as Gesture
21 | } else {
22 | println("Failed to get resource : $filePath")
23 | Gestures.Blink
24 | }
25 | }
26 |
27 | fun getAudioURL(path: String) : String {
28 | return if(isVirtual && runningFromIntelliJ) {
29 | "file:${File(".").canonicalPath + "/src/main/resources/sounds/"}$path"
30 | } else {
31 | "classpath:sounds/$path"
32 | }
33 | }
34 |
35 | fun _defineGesture(name: String? = null,
36 | strength: Double = 1.0,
37 | duration: Double = 1.0,
38 | defaultPriority: Int = 0,
39 | frameTimes: List? = null,
40 | audioURL: String? = null,
41 | texture: String? = null,
42 | ledPixel: Pixel? = null,
43 | definition: GestureBuilder.() -> Unit): Gesture {
44 | val gesture = defineGesture(name, strength, duration, defaultPriority, definition)
45 |
46 | if(frameTimes != null) {
47 | gesture.frames.add(Frame(frameTimes, false, audioURL, texture, texture, ledPixel))
48 | }
49 |
50 | return gesture
51 | }
52 |
--------------------------------------------------------------------------------
/Dog/src/main/kotlin/furhatos/app/dog/utils/smileBack.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.dog.utils
2 |
3 | /**
4 | * This can be included using include(SmileBackState)
5 | *
6 | * Todo. See if smiles should have rondomized strength
7 | */
8 |
9 | import furhatos.flow.kotlin.*
10 | import furhatos.skills.emotions.UserGestures
11 | import gestures.indefiniteBigSmile
12 | import gestures.indefiniteSmile
13 | import gestures.stopSmile
14 | import kotlinx.coroutines.GlobalScope
15 | import kotlinx.coroutines.delay
16 | import kotlinx.coroutines.launch
17 |
18 | var flagSmileBack = true
19 |
20 | var smileProbability = 0.40
21 | var bigSmileProbability = 0.60
22 | /**
23 | * Noticed that 3000ms was too quick, that's why this is set to 5000ms
24 | */
25 | var smileBlockDelay: Long = 5000
26 | var smilingIsAllowed = true
27 |
28 | val SmileBackState = state {
29 |
30 | /**
31 | * If a smile is allowed, one of three things will happen
32 | * Big Smile
33 | * Smile
34 | * or Nothing
35 | */
36 | onUserGesture(UserGestures.Smile, cond = { smilingIsAllowed }) {
37 | val randomValue = Math.random()
38 | when {
39 | randomValue < bigSmileProbability -> {
40 | smilingIsAllowed = false
41 | furhat.gesture(indefiniteBigSmile)
42 | resetAllowedToSmile()
43 | }
44 | randomValue < smileProbability + bigSmileProbability -> {
45 | smilingIsAllowed = false
46 | furhat.gesture(indefiniteSmile)
47 | resetAllowedToSmile()
48 | }
49 | else -> {
50 | //No Smile
51 | }
52 | }
53 | //reentry()
54 | }
55 |
56 | onUserGestureEnd(UserGestures.Smile) {
57 | furhat.gesture(stopSmile)
58 | //reentry()
59 | }
60 | }
61 |
62 | /**
63 | * Resets the variable to allow for a smile after a default of smileBlockDelay ms
64 | */
65 | fun resetAllowedToSmile() {
66 | GlobalScope.launch {
67 | //val currentTime = System.currentTimeMillis()
68 | delay(smileBlockDelay)
69 | smilingIsAllowed = true
70 | }
71 | }
72 |
73 |
74 |
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Medium_dog_growl_vicious_02.pho:
--------------------------------------------------------------------------------
1 | {
2 | "class": "furhatos.records.Transcription",
3 | "phones": [
4 | {
5 | "prominent": false,
6 | "name": "EH",
7 | "end": 0.76,
8 | "start": 0.0,
9 | "word": "eh"
10 | },
11 | {
12 | "prominent": false,
13 | "name": "_s",
14 | "end": 0.82,
15 | "start": 0.29
16 | },
17 | {
18 | "prominent": false,
19 | "name": "",
20 | "end": 0.84,
21 | "start": 0.82
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Medium_dog_growl_vicious_02.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Medium_dog_growl_vicious_02.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Medium_dog_growl_vicious_03.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Medium_dog_growl_vicious_03.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Medium_dog_growl_vicious_04.pho:
--------------------------------------------------------------------------------
1 | {
2 | "class": "furhatos.records.Transcription",
3 | "phones": [
4 | {
5 | "end": 0.17,
6 | "start": 0.0,
7 | "name": "_s",
8 | "prominent": false
9 | },
10 | {
11 | "end": 0.63,
12 | "word": "eh",
13 | "start": 0.17,
14 | "name": "EH",
15 | "prominent": false
16 | },
17 | {
18 | "end": 0.92,
19 | "start": 0.63,
20 | "name": "_s",
21 | "prominent": false
22 | },
23 | {
24 | "end": 0.94,
25 | "start": 0.92,
26 | "name": "",
27 | "prominent": false
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Medium_dog_growl_vicious_04.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Medium_dog_growl_vicious_04.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Medium_large_dog_yawning_02.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Medium_large_dog_yawning_02.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Meow.pho:
--------------------------------------------------------------------------------
1 | {
2 | "phones": [
3 | {
4 | "start": 0.0,
5 | "end": 0.64,
6 | "prominent": false,
7 | "name": "_s"
8 | },
9 | {
10 | "start": 0.64,
11 | "end": 0.67,
12 | "prominent": false,
13 | "word": "yay",
14 | "name": "J"
15 | },
16 | {
17 | "start": 0.67,
18 | "end": 0.87,
19 | "prominent": false,
20 | "name": "EI"
21 | },
22 | {
23 | "start": 0.87,
24 | "end": 1.22,
25 | "prominent": false,
26 | "name": "_s"
27 | },
28 | {
29 | "start": 1.22,
30 | "end": 1.237625,
31 | "prominent": false,
32 | "name": ""
33 | }
34 | ],
35 | "class": "furhatos.records.Transcription"
36 | }
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Meow.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Meow.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Panting1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Panting1.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_1_bark.pho:
--------------------------------------------------------------------------------
1 | {
2 | "class": "furhatos.records.Transcription",
3 | "phones": [
4 | {
5 | "end": 0.04,
6 | "name": "H",
7 | "start": 0.0,
8 | "prominent": false,
9 | "word": "ha"
10 | },
11 | {
12 | "end": 0.62,
13 | "name": "AH",
14 | "start": 0.04,
15 | "prominent": false
16 | },
17 | {
18 | "end": 1.1,
19 | "name": "_s",
20 | "start": 0.62,
21 | "prominent": false
22 | },
23 | {
24 | "end": 1.12,
25 | "name": "",
26 | "start": 1.1,
27 | "prominent": false
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_1_bark.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Small_dog_1_bark.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_2_barks.pho:
--------------------------------------------------------------------------------
1 | {
2 | "class": "furhatos.records.Transcription",
3 | "phones": [
4 | {
5 | "start": 0.0,
6 | "word": "how",
7 | "end": 0.12,
8 | "name": "H",
9 | "prominent": false
10 | },
11 | {
12 | "start": 0.12,
13 | "end": 0.22,
14 | "name": "AU",
15 | "prominent": false
16 | },
17 | {
18 | "start": 0.22,
19 | "end": 0.31,
20 | "name": "_s",
21 | "prominent": false
22 | },
23 | {
24 | "start": 0.31,
25 | "word": "how",
26 | "end": 0.36,
27 | "name": "H",
28 | "prominent": false
29 | },
30 | {
31 | "start": 0.36,
32 | "end": 0.49,
33 | "name": "AU",
34 | "prominent": false
35 | },
36 | {
37 | "start": 0.49,
38 | "end": 0.68,
39 | "name": "_s",
40 | "prominent": false
41 | },
42 | {
43 | "start": 0.68,
44 | "end": 0.7,
45 | "name": "",
46 | "prominent": false
47 | }
48 | ]
49 | }
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_2_barks.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Small_dog_2_barks.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_3_barks.pho:
--------------------------------------------------------------------------------
1 | {
2 | "class": "furhatos.records.Transcription",
3 | "phones": [
4 | {
5 | "start": 0.0,
6 | "end": 0.15,
7 | "name": "H",
8 | "word": "how",
9 | "prominent": false
10 | },
11 | {
12 | "start": 0.15,
13 | "end": 0.19,
14 | "name": "AU",
15 | "prominent": false
16 | },
17 | {
18 | "start": 0.19,
19 | "end": 0.32,
20 | "name": "_s",
21 | "prominent": false
22 | },
23 | {
24 | "start": 0.32,
25 | "end": 0.36,
26 | "name": "H",
27 | "word": "how",
28 | "prominent": false
29 | },
30 | {
31 | "start": 0.36,
32 | "end": 0.51,
33 | "name": "AU",
34 | "prominent": false
35 | },
36 | {
37 | "start": 0.51,
38 | "end": 0.68,
39 | "name": "_s",
40 | "prominent": false
41 | },
42 | {
43 | "start": 0.68,
44 | "end": 0.72,
45 | "name": "H",
46 | "word": "how",
47 | "prominent": false
48 | },
49 | {
50 | "start": 0.72,
51 | "end": 0.85,
52 | "name": "AU",
53 | "prominent": false
54 | },
55 | {
56 | "start": 0.85,
57 | "end": 0.98,
58 | "name": "_s",
59 | "prominent": false
60 | },
61 | {
62 | "start": 0.98,
63 | "end": 1.0,
64 | "name": "",
65 | "prominent": false
66 | }
67 | ]
68 | }
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_3_barks.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Small_dog_3_barks.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_single_growl_non_aggressive_02.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Small_dog_single_growl_non_aggressive_02.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_single_growl_non_aggressive_04.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Small_dog_single_growl_non_aggressive_04.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_whining_01.pho:
--------------------------------------------------------------------------------
1 | {
2 | "class": "furhatos.records.Transcription",
3 | "phones": [
4 | {
5 | "start": 0.0,
6 | "prominent": false,
7 | "name": "H",
8 | "word": "whimpering1",
9 | "end": 0.03
10 | },
11 | {
12 | "start": 0.03,
13 | "prominent": false,
14 | "name": "W",
15 | "end": 0.06
16 | },
17 | {
18 | "start": 0.06,
19 | "prominent": false,
20 | "name": "IH",
21 | "end": 0.09
22 | },
23 | {
24 | "start": 0.09,
25 | "prominent": false,
26 | "name": "M",
27 | "end": 0.12
28 | },
29 | {
30 | "start": 0.12,
31 | "prominent": false,
32 | "name": "P",
33 | "end": 0.25
34 | },
35 | {
36 | "start": 0.25,
37 | "prominent": true,
38 | "name": "ER",
39 | "end": 0.47
40 | },
41 | {
42 | "start": 0.47,
43 | "prominent": true,
44 | "name": "IH",
45 | "end": 0.52
46 | },
47 | {
48 | "start": 0.52,
49 | "prominent": false,
50 | "name": "NG",
51 | "end": 0.62
52 | },
53 | {
54 | "start": 0.62,
55 | "prominent": false,
56 | "name": "AH",
57 | "end": 0.66
58 | },
59 | {
60 | "start": 0.66,
61 | "prominent": false,
62 | "name": "N",
63 | "end": 0.73
64 | },
65 | {
66 | "start": 0.73,
67 | "prominent": false,
68 | "name": "",
69 | "end": 0.75
70 | }
71 | ]
72 | }
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_whining_01.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Small_dog_whining_01.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_whining_02.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Small_dog_whining_02.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_whining_03.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Small_dog_whining_03.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Small_dog_whining_05.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Small_dog_whining_05.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Sniffing1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Sniffing1.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Sniffing2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Sniffing2.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/Sniffing3.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/Sniffing3.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/d-17a.pho:
--------------------------------------------------------------------------------
1 | {
2 | "class": "furhatos.records.Transcription",
3 | "phones": [
4 | {
5 | "name": "AH",
6 | "start": 0.0,
7 | "end": 0.53,
8 | "prominent": true,
9 | "word": "ooh"
10 | },
11 | {
12 | "name": "AH",
13 | "start": 0.53,
14 | "end": 0.57,
15 | "prominent": false
16 | },
17 | {
18 | "name": "H",
19 | "start": 0.57,
20 | "end": 0.64,
21 | "prominent": false
22 | },
23 | {
24 | "name": "_s",
25 | "start": 0.64,
26 | "end": 0.83,
27 | "prominent": false
28 | },
29 | {
30 | "name": "_s",
31 | "start": 0.83,
32 | "end": 0.8458125,
33 | "prominent": false
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/d-17a.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/d-17a.wav
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/d-17b.pho:
--------------------------------------------------------------------------------
1 | {
2 | "class": "furhatos.records.Transcription",
3 | "phones": [
4 | {
5 | "name": "W",
6 | "start": 0.0,
7 | "end": 0.06,
8 | "prominent": false,
9 | "word": "wah"
10 | },
11 | {
12 | "name": "AH",
13 | "start": 0.06,
14 | "end": 0.26,
15 | "prominent": false
16 | },
17 | {
18 | "name": "H",
19 | "start": 0.26,
20 | "end": 0.43,
21 | "prominent": false
22 | },
23 | {
24 | "name": "_s",
25 | "start": 0.43,
26 | "end": 0.64,
27 | "prominent": false
28 | },
29 | {
30 | "name": "_s",
31 | "start": 0.64,
32 | "end": 0.6573125,
33 | "prominent": false
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/Dog/src/main/resources/sounds/d-17b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Dog/src/main/resources/sounds/d-17b.wav
--------------------------------------------------------------------------------
/FortuneTeller/assets/webTemplates/BASIC/css/style.css:
--------------------------------------------------------------------------------
1 | *{
2 | box-sizing: border-box;
3 | }
4 |
5 | body{
6 | background: linear-gradient(-60deg, #ff5858 0%, #f09819 100%);
7 | }
8 |
9 | .center{
10 | text-align: center;
11 | }
12 |
13 | p{
14 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
15 | font-size: 30px;
16 | font-weight: 700;
17 | color: #fff;
18 | }
--------------------------------------------------------------------------------
/FortuneTeller/assets/webTemplates/BASIC/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Furhat Skill
9 |
10 |
11 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/FortuneTeller/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "org.jetbrains.kotlin.jvm" version "1.8.21"
3 | id 'com.github.johnrengelman.shadow' version '2.0.4'
4 | }
5 |
6 | apply plugin: 'java'
7 | apply plugin: 'kotlin'
8 |
9 | //Defines what version of Java to use.
10 | sourceCompatibility = 1.8
11 |
12 | //Defines how Kotlin should compile.
13 | compileKotlin {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 |
17 | kotlinOptions {
18 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
19 | jvmTarget = "1.8"
20 | apiVersion = "1.8"
21 | languageVersion = "1.8"
22 | }
23 | }
24 |
25 | //Defines how Kotlin should compile when testingTry to keep it the same as compileKotlin.
26 | compileTestKotlin {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 |
30 | kotlinOptions {
31 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
32 | jvmTarget = "1.8"
33 | apiVersion = "1.8"
34 | languageVersion = "1.8"
35 | }
36 | }
37 |
38 | repositories {
39 | mavenLocal()
40 | mavenCentral()
41 | maven { url "https://s3-eu-west-1.amazonaws.com/furhat-maven/releases"}
42 | maven { url 'https://repo.gradle.org/gradle/libs-releases' }
43 | }
44 |
45 |
46 | dependencies {
47 | implementation 'com.furhatrobotics.furhatos:furhat-commons:2.7.0'
48 | }
49 |
50 | jar {
51 | def lowerCasedName = baseName.toLowerCase()
52 | def normalizedName = lowerCasedName.substring(0,1).toUpperCase() + lowerCasedName.substring(1)
53 | manifest.attributes(
54 | 'Class-Path': configurations.compileClasspath.collect { it.getName() }.join(' '),
55 | 'Main-Class': "furhatos.app.${lowerCasedName}.${normalizedName}Skill"
56 | )
57 | }
58 |
59 | //ShadowJar depends on jar being finished properly.
60 | shadowJar {
61 | manifest {
62 | exclude '**/Log4j2Plugins.dat'
63 | exclude '**/node_modules'
64 | }
65 | from "skill.properties"
66 | from "assets"
67 | extension 'skill'
68 | Properties properties = new Properties()
69 | properties.load(project.file('skill.properties').newDataInputStream())
70 | def version = properties.getProperty('version')
71 | def name = properties.getProperty('name')
72 | archiveName = "${name}_${version}.skill"
73 | }
74 |
--------------------------------------------------------------------------------
/FortuneTeller/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/FortuneTeller/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/FortuneTeller/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-bin.zip
6 |
--------------------------------------------------------------------------------
/FortuneTeller/skill.properties:
--------------------------------------------------------------------------------
1 | name = FortuneTeller
2 | version=1.1.1
3 | mainclass = furhatos.app.fortuneteller.FortunetellerSkill
4 | language = en-US
5 | logLevel = INFO
6 | #You may set this to a Furhat version.
7 | requiresVersion = false
8 | #Set to true if this skill should fail if there is no camera
9 | requiresCamera = false
10 | #Set to true if this skill should fail if there is no speaker
11 | requiresSpeaker = false
12 | #Set to true if this skill should fail if there is no microphone
13 | requiresMicrophone = false
14 | #Set to true if this skill should fail if there is no active recognizer
15 | requiresRecognizer = false
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/flow/init.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller.flow
2 |
3 | import furhatos.app.fortuneteller.flow.main.Idle
4 | import furhatos.app.fortuneteller.setting.activate
5 | import furhatos.app.fortuneteller.setting.fortuneTellerPersona
6 | import furhatos.app.fortuneteller.setting.maxNumberOfUsers
7 | import furhatos.flow.kotlin.state
8 | import furhatos.flow.kotlin.users
9 | import furhatos.flow.kotlin.voice.Voice
10 | import furhatos.util.Language
11 |
12 | val defaultVoice = "WillBadGuy22k_HQ"
13 | val theVoice = Voice(name = defaultVoice, language = Language.ENGLISH_US)
14 |
15 | val Init = state {
16 | onEntry {
17 | /** Set our default interaction parameters */
18 | users.setSimpleEngagementPolicy(0.5, 1.2, 1.2, 1.7, maxNumberOfUsers)
19 |
20 |
21 | /** Set our main character - defined in personas */
22 | activate(fortuneTellerPersona)
23 |
24 | /** start the interaction */
25 | goto(Idle)
26 | }
27 | }
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/flow/main/endReading.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller.flow.main
2 |
3 | import furhatos.app.fortuneteller.flow.served
4 | import furhatos.app.fortuneteller.gestures.FallAsleep
5 | import furhatos.app.fortuneteller.setting.lookDown
6 | import furhatos.app.fortuneteller.setting.lookForward
7 | import furhatos.event.EventSystem
8 | import furhatos.event.actions.ActionAttend
9 | import furhatos.event.actions.ActionGaze
10 | import furhatos.flow.kotlin.furhat
11 | import furhatos.flow.kotlin.state
12 | import furhatos.flow.kotlin.users
13 | import java.awt.Color
14 |
15 | val EndReading = state {
16 | onEntry {
17 | furhat.attend(lookForward)
18 | delay(800)
19 | furhat.gesture(FallAsleep, priority = 10)
20 | delay(600)
21 | furhat.ledStrip.solid(Color(0, 0, 120))
22 | EventSystem.send(ActionAttend.Builder().location(lookDown).speed(ActionGaze.Speed.XSLOW).buildEvent())
23 | delay(2500)
24 | val unServedUsers = users.list.filter { !it.served }
25 | if (!unServedUsers.isEmpty()) {
26 | println("There are unserved users" + unServedUsers.size)
27 | goto(startReading(unServedUsers.random()))
28 | } else {
29 | goto(Idle)
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/flow/main/idle.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller.flow.main
2 |
3 | import furhatos.flow.kotlin.*
4 |
5 |
6 | val Idle: State = state {
7 |
8 | init {
9 | //if (users.count > 0) {
10 | //furhat.attend(users.random)
11 | //goto(Start)
12 | //}
13 | if (furhat.isVirtual() && users.hasAny() == false) {
14 | furhat.say("Add a Virtual User to start the interaction. ")
15 | }
16 | }
17 |
18 | onEntry {
19 | //furhat.attendNobody()
20 | }
21 |
22 | onUserEnter {
23 | goto(startReading(it))
24 | }
25 | }
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/flow/parent.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller.flow
2 |
3 | import furhatos.app.fortuneteller.flow.main.EndReading
4 | import furhatos.flow.kotlin.*
5 |
6 |
7 | val Parent: State = state {
8 |
9 | onUserLeave(instant = true) {
10 | if (it == users.current) {
11 | goto(EndReading)
12 | }
13 | /*if (users.count > 0) {
14 | if (it == users.current) {
15 | furhat.attend(users.other)
16 | goto(Start)
17 | } else {
18 | furhat.glance(it)
19 | }
20 | } else {
21 | goto(EndReading)
22 | }*/
23 | }
24 |
25 | onUserEnter(instant = true) {
26 | furhat.glance(it)
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/gestures/gestures.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller.gestures
2 |
3 | import furhatos.gestures.BasicParams
4 | import furhatos.gestures.defineGesture
5 |
6 | val cSmile = defineGesture("cSmile") {
7 | frame(0.5, persist = true) {
8 | BasicParams.BLINK_LEFT to 1.0
9 | BasicParams.BLINK_RIGHT to 1.0
10 | }
11 |
12 | }
13 |
14 | fun rollHead(strength: Double = 1.0, duration: Double = 1.0) =
15 | defineGesture("rollHead") {
16 | frame(0.4, duration) {
17 | BasicParams.NECK_ROLL to strength
18 | }
19 | reset(duration + 0.1)
20 | }
21 |
22 | val FallAsleep = defineGesture("FallAsleep") {
23 | frame(0.5, persist = true) {
24 | BasicParams.BLINK_LEFT to 1.0
25 | BasicParams.BLINK_RIGHT to 1.0
26 | }
27 |
28 | }
29 |
30 | val MySmile = defineGesture("MySmile") {
31 | frame(0.32, 0.72) {
32 | BasicParams.SMILE_CLOSED to 2.0
33 | }
34 | frame(0.2, 0.72) {
35 | BasicParams.BROW_UP_LEFT to 4.0
36 | BasicParams.BROW_UP_RIGHT to 4.0
37 | }
38 | frame(0.16, 0.72) {
39 | BasicParams.BLINK_LEFT to 1.0
40 | BasicParams.BLINK_RIGHT to 0.1
41 | }
42 | reset(1.04)
43 | }
44 |
45 | val TripleBlink = defineGesture("TripleBlink") {
46 | frame(0.1, 0.3) {
47 | BasicParams.BLINK_LEFT to 1.0
48 | BasicParams.BLINK_RIGHT to 1.0
49 | }
50 | frame(0.3, 0.5) {
51 | BasicParams.BLINK_LEFT to 0.1
52 | BasicParams.BLINK_RIGHT to 0.1
53 | }
54 | frame(0.5, 0.7) {
55 | BasicParams.BLINK_LEFT to 1.0
56 | BasicParams.BLINK_RIGHT to 1.0
57 | }
58 | frame(0.7, 0.9) {
59 | BasicParams.BLINK_LEFT to 0.1
60 | BasicParams.BLINK_RIGHT to 0.1
61 | BasicParams.BROW_UP_LEFT to 2.0
62 | BasicParams.BROW_UP_RIGHT to 2.0
63 | }
64 | frame(0.9, 1.1) {
65 | BasicParams.BLINK_LEFT to 1.0
66 | BasicParams.BLINK_RIGHT to 1.0
67 | }
68 | frame(1.1, 1.4) {
69 | BasicParams.BLINK_LEFT to 0.1
70 | BasicParams.BLINK_RIGHT to 0.1
71 | }
72 | frame(1.4, 1.5) {
73 | BasicParams.BROW_UP_LEFT to 0
74 | BasicParams.BROW_UP_RIGHT to 0
75 | }
76 | reset(1.5)
77 | }
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/main.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller
2 |
3 | import furhatos.app.fortuneteller.flow.*
4 | import furhatos.skills.Skill
5 | import furhatos.flow.kotlin.*
6 |
7 | class FortunetellerSkill : Skill() {
8 | override fun start() {
9 | Flow().run(Init)
10 | }
11 | }
12 |
13 | fun main(args: Array) {
14 | Skill.main(args)
15 | }
16 |
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/nlu/nlu.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller.nlu
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/setting/engagementParams.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller.setting
2 |
3 | import furhatos.records.Location
4 |
5 | val maxNumberOfUsers = 2
6 | val distanceToEngage = 1.0 // not used, we use a more complex shape of the interaction space
7 |
8 | /** Locations **/
9 | val lookForward = Location(0.0, 0.0, 1.0)
10 | val lookDown = Location(0.0, -10.0, 1.0)
11 |
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/setting/personas.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller.setting
2 |
3 | import furhatos.flow.kotlin.FlowControlRunner
4 | import furhatos.flow.kotlin.furhat
5 | import furhatos.flow.kotlin.voice.PollyNeuralVoice
6 | import furhatos.flow.kotlin.voice.Voice
7 | import furhatos.util.Language
8 |
9 | class Persona(val name: String, val mask: String = "adult", val face: List, val voice: List)
10 |
11 | fun FlowControlRunner.activate(persona: Persona) {
12 | for (voice in persona.voice) {
13 | if (voice.isAvailable) {
14 | furhat.voice = voice
15 | break
16 | }
17 | }
18 |
19 | for (face in persona.face) {
20 | if (furhat.faces.get(persona.mask)?.contains(face)!!) {
21 | furhat.character = face
22 | break
23 | }
24 | }
25 | }
26 |
27 | val furhatPersona = Persona(
28 | name = "Furhat",
29 | face = listOf("Alex",
30 | "default"),
31 | voice = listOf(
32 | PollyNeuralVoice.Matthew(),
33 | PollyNeuralVoice.Joanna()).shuffled() // randomize what voice to select
34 | )
35 |
36 | val fortuneTellerPersona = Persona(
37 | name = "FortuneTeller",
38 | face = listOf(
39 | "Titan",
40 | "default"),
41 | voice = listOf(
42 | Voice(name = "WillBadGuy22k_HQ", language = Language.ENGLISH_US, rate = 1.1, pitch = "high"),
43 | Voice(name = "Kimberly-neural", language = Language.ENGLISH_US, rate = 1.0, pitch = "low")) // Kimberly is backup voice if Acapela voice is not available
44 | )
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/setting/users.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller.flow
2 |
3 | import furhatos.flow.kotlin.NullSafeUserDataDelegate
4 | import furhatos.records.User
5 |
6 | var User.served by NullSafeUserDataDelegate { false }
--------------------------------------------------------------------------------
/FortuneTeller/src/main/kotlin/furhatos/app/fortuneteller/utils.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.fortuneteller
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Interviewee/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | /out
3 | /build
4 | build/
5 | /.gradle
6 | /logs
7 | /.idea
8 | save_flow.xml
9 | *.iml
10 |
--------------------------------------------------------------------------------
/Interviewee/README.md:
--------------------------------------------------------------------------------
1 | # Interviewee
2 | Wizard and interaction for Furhat to give an interview
3 |
4 | ## Description
5 | This is a wizarded interaction for preparing the robot with answers to questions in a interview situation
6 |
7 | ## Usage
8 | Max number of users is set to: 1
9 | Requires a "wizard" to control the robot in real-time
--------------------------------------------------------------------------------
/Interviewee/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | plugins {
3 | id "org.jetbrains.kotlin.jvm" version "1.8.21"
4 | id 'com.github.johnrengelman.shadow' version '2.0.4'
5 | }
6 |
7 | apply plugin: 'java'
8 | apply plugin: 'kotlin'
9 |
10 | //Defines what version of Java to use.
11 | sourceCompatibility = 1.8
12 |
13 | //Defines how Kotlin should compile.
14 | compileKotlin {
15 | sourceCompatibility = JavaVersion.VERSION_1_8
16 | targetCompatibility = JavaVersion.VERSION_1_8
17 |
18 | kotlinOptions {
19 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
20 | jvmTarget = "1.8"
21 | apiVersion = "1.8"
22 | languageVersion = "1.8"
23 | }
24 | }
25 |
26 | //Defines how Kotlin should compile when testingTry to keep it the same as compileKotlin.
27 | compileTestKotlin {
28 | sourceCompatibility = JavaVersion.VERSION_1_8
29 | targetCompatibility = JavaVersion.VERSION_1_8
30 |
31 | kotlinOptions {
32 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
33 | jvmTarget = "1.8"
34 | apiVersion = "1.8"
35 | languageVersion = "1.8"
36 | }
37 | }
38 |
39 | repositories {
40 | mavenLocal()
41 | mavenCentral()
42 | maven { url "https://s3-eu-west-1.amazonaws.com/furhat-maven/releases"}
43 | maven { url 'https://repo.gradle.org/gradle/libs-releases' }
44 | }
45 |
46 | dependencies {
47 | implementation 'com.furhatrobotics.furhatos:furhat-commons:2.7.0'
48 | }
49 |
50 | //These new blocks are needed to package your project into a working skill file.
51 | jar {
52 | def lowerCasedName = baseName.toLowerCase()
53 | def normalizedName = lowerCasedName.substring(0,1).toUpperCase() + lowerCasedName.substring(1)
54 | manifest.attributes(
55 | 'Class-Path': configurations.compileClasspath.collect { it.getName() }.join(' '),
56 | 'Main-Class': "furhatos.app.${lowerCasedName}.${normalizedName}Skill"
57 | )
58 | }
59 |
60 | shadowJar {
61 | manifest {
62 | exclude '**/Log4j2Plugins.dat'
63 | exclude '**/node_modules'
64 | }
65 | from "skill.properties"
66 | from "assets"
67 | extension 'skill'
68 | }
69 |
--------------------------------------------------------------------------------
/Interviewee/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Interviewee/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Interviewee/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 13 09:14:30 CET 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-all.zip
7 |
--------------------------------------------------------------------------------
/Interviewee/skill.properties:
--------------------------------------------------------------------------------
1 | name = Interview
2 | mainclass = furhatos.app.interview.InterviewSkill
3 | language = en-US
4 | logLevel = INFO
5 | #You may set this to a Furhat version.
6 | requiresVersion = false
7 | #Set to true if this skill should fail if there is no camera
8 | requiresCamera = false
9 | #Set to true if this skill should fail if there is no speaker
10 | requiresSpeaker = false
11 | #Set to true if this skill should fail if there is no microphone
12 | requiresMicrophone = false
13 | #Set to true if this skill should fail if there is no active recognizer
14 | requiresRecognizer = false
--------------------------------------------------------------------------------
/Interviewee/src/main/kotlin/furhatos/app/interview/flow/changeMask.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.interview.flow
2 |
3 | import furhatos.app.interview.flow.main.Interview
4 | import furhatos.app.interview.setting.maskChanging
5 | import furhatos.flow.kotlin.Color
6 | import furhatos.flow.kotlin.Section
7 | import furhatos.flow.kotlin.furhat
8 | import furhatos.flow.kotlin.state
9 |
10 | fun ChangeMask(model: String) = state(Interview) {
11 | onEntry {
12 | maskChanging = true
13 | }
14 |
15 | onButton("$model mask is on!", section = Section.RIGHT, color = Color.Red) {
16 | furhat.setModel(model)
17 | terminate()
18 | }
19 |
20 | onExit {
21 | maskChanging = false
22 | }
23 | }
--------------------------------------------------------------------------------
/Interviewee/src/main/kotlin/furhatos/app/interview/flow/generalAnswers.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.interview.flow
2 |
3 | import furhatos.flow.kotlin.Color
4 | import furhatos.flow.kotlin.Section
5 | import furhatos.flow.kotlin.furhat
6 | import furhatos.flow.kotlin.partialState
7 | import furhatos.gestures.Gestures
8 |
9 | val statements = listOf(
10 | "Yes",
11 | "No",
12 | "Maybe",
13 | "Absolutely",
14 | "Hi",
15 | "Bye",
16 | "Cheers",
17 | "Do you?"
18 | )
19 |
20 | val generalAnswers = partialState {
21 | statements.forEach { statement ->
22 | onButton(label = statement, section = Section.RIGHT, color = Color.Green) {
23 | furhat.say {
24 | +statement
25 | Gestures.BigSmile
26 | }
27 | }
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/Interviewee/src/main/kotlin/furhatos/app/interview/flow/init.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.interview.flow
2 |
3 | import furhatos.app.interview.flow.main.Interview
4 | import furhatos.app.interview.setting.distanceToEngage
5 | import furhatos.app.interview.setting.maxNumberOfUsers
6 | import furhatos.flow.kotlin.State
7 | import furhatos.flow.kotlin.state
8 | import furhatos.flow.kotlin.users
9 |
10 | val Init: State = state() {
11 | init {
12 | /** Set our default interaction parameters */
13 | users.setSimpleEngagementPolicy(distanceToEngage, maxNumberOfUsers)
14 |
15 | /** start the interaction */
16 | goto(Interview)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Interviewee/src/main/kotlin/furhatos/app/interview/main.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.interview
2 |
3 | import furhatos.app.interview.flow.Init
4 | import furhatos.skills.Skill
5 | import furhatos.flow.kotlin.*
6 |
7 | class InterviewSkill : Skill() {
8 | override fun start() {
9 | Flow().run(Init)
10 | }
11 | }
12 |
13 | fun main(args: Array) {
14 | Skill.main(args)
15 | }
16 |
--------------------------------------------------------------------------------
/Interviewee/src/main/kotlin/furhatos/app/interview/setting/engagementParams.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.interview.setting
2 |
3 | val maxNumberOfUsers = 1
4 | val distanceToEngage = 1.5
--------------------------------------------------------------------------------
/Interviewee/src/main/kotlin/furhatos/app/interview/setting/settings.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.interview.setting
2 |
3 | import furhatos.flow.kotlin.voice.PollyVoice
4 | import furhatos.records.Location
5 | import furhatos.util.Gender
6 | import furhatos.util.Language
7 |
8 | val interviewerLocation = Location(0.0, 0.0, 1.0)
9 | const val interviewerName = ", master human" // see the first question
10 | const val confederateName = "human" // used to ask for a mask change
11 | var maskChanging = false
12 | val interval = 2000..4000
13 | const val amplitudeUserPresent = 0.05
14 | const val amplitudeUserAbsent = 0.1
15 |
16 | val femaleVoice = PollyVoice.Joanna()
17 | val maleVoice = PollyVoice.Matthew()
18 |
--------------------------------------------------------------------------------
/Interviewee/src/main/kotlin/furhatos/app/interview/util.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.interview.flow
2 |
3 | import furhatos.records.Location
4 |
5 | fun getRelativeRandomLocation(location : Location, amplitude : Double) : Location {
6 | val locations = mutableListOf()
7 | for (x in 0..3) {
8 | for (y in 0..3) {
9 | val _x = x * amplitude
10 | val _y = y * amplitude / 3 // Smaller changes on Y-axis
11 | locations.add(location.add(Location(_x, _y, 0.0)))
12 | locations.add(location.subtract(Location(_x, -_y, 0.0)))
13 | }
14 | }
15 | return locations.shuffled().first()
16 | }
--------------------------------------------------------------------------------
/JokeBot/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | /out
3 | /build
4 | build/
5 | /.gradle
6 | /logs
7 | /.idea
8 | save_flow.xml
9 | *.iml
10 |
--------------------------------------------------------------------------------
/JokeBot/README.md:
--------------------------------------------------------------------------------
1 | # Jokebot
2 | A root learning humour
3 |
4 | ## Description
5 | A skill where the robot tell jokes and checks to see if you are smiling or not.
6 |
7 | ## Usage
8 | Max number of users is set to: 2
9 | The smile detection only works on the physical robot where there is a camera feed
--------------------------------------------------------------------------------
/JokeBot/assets/webTemplates/BASIC/css/style.css:
--------------------------------------------------------------------------------
1 | *{
2 | box-sizing: border-box;
3 | }
4 |
5 | body{
6 | background: linear-gradient(-60deg, #ff5858 0%, #f09819 100%);
7 | }
8 |
9 | .center{
10 | text-align: center;
11 | }
12 |
13 | p{
14 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
15 | font-size: 30px;
16 | font-weight: 700;
17 | color: #fff;
18 | }
--------------------------------------------------------------------------------
/JokeBot/assets/webTemplates/BASIC/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Furhat Skill
9 |
10 |
11 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/JokeBot/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "org.jetbrains.kotlin.jvm" version "1.8.21"
3 | id 'com.github.johnrengelman.shadow' version '2.0.4'
4 | }
5 |
6 | apply plugin: 'java'
7 | apply plugin: 'kotlin'
8 |
9 | //Defines what version of Java to use.
10 | sourceCompatibility = 1.8
11 |
12 | //Defines how Kotlin should compile.
13 | compileKotlin {
14 | sourceCompatibility = JavaVersion.VERSION_1_8
15 | targetCompatibility = JavaVersion.VERSION_1_8
16 |
17 | kotlinOptions {
18 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
19 | jvmTarget = "1.8"
20 | apiVersion = "1.8"
21 | languageVersion = "1.8"
22 | }
23 | }
24 |
25 | //Defines how Kotlin should compile when testingTry to keep it the same as compileKotlin.
26 | compileTestKotlin {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 |
30 | kotlinOptions {
31 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
32 | jvmTarget = "1.8"
33 | apiVersion = "1.8"
34 | languageVersion = "1.8"
35 | }
36 | }
37 |
38 | repositories {
39 | mavenLocal()
40 | mavenCentral()
41 | maven { url "https://s3-eu-west-1.amazonaws.com/furhat-maven/releases"}
42 | maven { url 'https://repo.gradle.org/gradle/libs-releases' }
43 | }
44 |
45 | dependencies {
46 | implementation 'com.furhatrobotics.furhatos:furhat-commons:2.7.0'
47 | implementation 'com.google.code.gson:gson:2.8.6'
48 | }
49 |
50 | jar {
51 | def lowerCasedName = baseName.toLowerCase()
52 | def normalizedName = lowerCasedName.substring(0,1).toUpperCase() + lowerCasedName.substring(1)
53 | manifest.attributes(
54 | 'Class-Path': configurations.compileClasspath.collect { it.getName() }.join(' '),
55 | 'Main-Class': "furhatos.app.${lowerCasedName}.${normalizedName}Skill"
56 | )
57 | }
58 |
59 | //ShadowJar depends on jar being finished properly.
60 | shadowJar {
61 | manifest {
62 | exclude '**/Log4j2Plugins.dat'
63 | exclude '**/node_modules'
64 | }
65 | from "skill.properties"
66 | from "assets"
67 | extension 'skill'
68 | }
69 |
--------------------------------------------------------------------------------
/JokeBot/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/JokeBot/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/JokeBot/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jun 15 14:40:14 CEST 2020
2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-all.zip
3 | distributionBase=GRADLE_USER_HOME
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/JokeBot/skill.properties:
--------------------------------------------------------------------------------
1 | name = JokeBot
2 | mainclass = furhatos.app.jokebot.JokebotSkill
3 | language = en-US
4 | logLevel = INFO
5 | #You may set this to a Furhat version.
6 | requiresVersion = false
7 | #Set to true if this skill should fail if there is no camera
8 | requiresCamera = false
9 | #Set to true if this skill should fail if there is no speaker
10 | requiresSpeaker = false
11 | #Set to true if this skill should fail if there is no microphone
12 | requiresMicrophone = false
13 | #Set to true if this skill should fail if there is no active recognizer
14 | requiresRecognizer = false
--------------------------------------------------------------------------------
/JokeBot/src/main/kotlin/furhatos/app/jokebot/flow/init.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.jokebot.flow
2 |
3 | import furhatos.app.jokebot.flow.main.Idle
4 | import furhatos.app.jokebot.setting.activate
5 | import furhatos.app.jokebot.setting.distanceToEngage
6 | import furhatos.app.jokebot.setting.mainPersona
7 | import furhatos.app.jokebot.setting.maxNumberOfUsers
8 | import furhatos.flow.kotlin.State
9 | import furhatos.flow.kotlin.state
10 | import furhatos.flow.kotlin.users
11 |
12 | val Init: State = state() {
13 | init {
14 | /** Set our default interaction parameters */
15 | users.setSimpleEngagementPolicy(distanceToEngage, maxNumberOfUsers)
16 |
17 | /** Set our main character - defined in personas */
18 | activate(mainPersona)
19 |
20 | /** start the interaction */
21 | goto(Idle)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/JokeBot/src/main/kotlin/furhatos/app/jokebot/flow/main/idle.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.jokebot.flow.main
2 |
3 | import furhatos.flow.kotlin.*
4 |
5 | val Idle: State = state {
6 |
7 | init {
8 | when {
9 | users.count > 0 -> {
10 | furhat.attend(users.random)
11 | goto(Start)
12 | }
13 | users.count == 0 && furhat.isVirtual() -> goto(Start) // if the skill is run on virtual furhat, ignore if there are no users and start anyway.
14 | users.count == 0 && !furhat.isVirtual() -> furhat.say("I can't see anyone. Step closer please. ")
15 | }
16 | }
17 |
18 | onEntry {
19 | furhat.attendNobody()
20 | }
21 |
22 | onUserEnter {
23 | furhat.attend(it)
24 | goto(Start)
25 | }
26 | }
--------------------------------------------------------------------------------
/JokeBot/src/main/kotlin/furhatos/app/jokebot/flow/main/start.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.jokebot.flow.main
2 |
3 | import furhatos.app.jokebot.flow.Parent
4 | import furhatos.nlu.common.*
5 | import furhatos.flow.kotlin.*
6 |
7 | val Start : State = state(Parent) {
8 |
9 | onEntry {
10 | furhat.ask("Hi there.")
11 | }
12 |
13 | onResponse {
14 | goto(AreYouHappy)
15 | }
16 |
17 | onNoResponse {
18 | goto(AreYouHappy)
19 | }
20 | }
21 |
22 | val AreYouHappy: State = state(Parent) {
23 |
24 | onEntry {
25 | furhat.ask("I am wondering, are you happy today?")
26 | }
27 |
28 | onResponse {
29 | furhat.say("Great to hear, then you are in the right mood!")
30 | goto(RequestJokeTest)
31 | }
32 |
33 | onResponse {
34 | furhat.say("I'm sorry to hear that. Hmm, perhaps we can do something to cheer you up.")
35 | goto(RequestJokeTest)
36 | }
37 |
38 | onResponse {
39 | furhat.say("Perhaps we can try to increase your happiness a few notches.")
40 | goto(RequestJokeTest)
41 | }
42 | }
43 |
44 | val RequestJokeTest: State = state(Parent) {
45 | onEntry {
46 | furhat.ask("I’m trying to learn some humor you see. So. Could I test a few jokes on you?")
47 | }
48 |
49 | onResponse {
50 | furhat.say("Awesome")
51 | goto(JokeSequence)
52 | }
53 |
54 | onResponse {
55 | furhat.say("Oh, that’s a shame.")
56 | goto(Idle)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/JokeBot/src/main/kotlin/furhatos/app/jokebot/flow/parent.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.jokebot.flow
2 |
3 | import furhatos.app.jokebot.flow.main.Idle
4 | import furhatos.app.jokebot.setting.SmileBack
5 | import furhatos.flow.kotlin.*
6 |
7 | val Parent: State = state() {
8 |
9 | include(SmileBack)
10 | onUserLeave(instant = true) {
11 | when {
12 | users.count == 0 -> goto(Idle)
13 | it == users.current -> furhat.attend(users.other)
14 | }
15 | }
16 |
17 | onUserEnter(instant = true) {
18 | furhat.glance(it)
19 | }
20 | }
--------------------------------------------------------------------------------
/JokeBot/src/main/kotlin/furhatos/app/jokebot/jokes/gestures.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.jokebot.jokes
2 |
3 | import furhatos.gestures.BasicParams
4 | import furhatos.gestures.defineGesture
5 |
6 |
7 | /**
8 | * The gestures are defined here.
9 | */
10 |
11 | //Big Smile that doesnt stop
12 | val indefiniteBigSmile = defineGesture {
13 | frame(0.32, 0.64, persist = true) {
14 | BasicParams.BROW_UP_LEFT to 1.0
15 | BasicParams.BROW_UP_RIGHT to 1.0
16 | BasicParams.SMILE_OPEN to 0.4
17 | BasicParams.SMILE_CLOSED to 0.7
18 | }
19 | }
20 |
21 | //Small smile that doesnt stop
22 | val indefiniteSmile = defineGesture {
23 | frame(0.32, 0.72, persist = true) {
24 | BasicParams.SMILE_CLOSED to 0.5
25 | }
26 | frame(0.2, 0.72){
27 | BasicParams.BROW_UP_LEFT to 1.0
28 | BasicParams.BROW_UP_RIGHT to 1.0
29 | }
30 | frame(0.16, 0.72){
31 | BasicParams.BLINK_LEFT to 0.1
32 | BasicParams.BLINK_RIGHT to 0.1
33 | }
34 | }
35 |
36 | //No more smiling
37 | val stopSmile = defineGesture {
38 | frame(0.32, 0.64) {
39 | BasicParams.BROW_UP_LEFT to 0.0
40 | BasicParams.BROW_UP_RIGHT to 0.0
41 | BasicParams.SMILE_OPEN to 0.0
42 | BasicParams.SMILE_CLOSED to 0.0
43 | }
44 | }
--------------------------------------------------------------------------------
/JokeBot/src/main/kotlin/furhatos/app/jokebot/main.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.jokebot
2 |
3 | import furhatos.app.jokebot.flow.Init
4 | import furhatos.flow.kotlin.Flow
5 | import furhatos.skills.Skill
6 |
7 | class JokebotSkill : Skill() {
8 | override fun start() {
9 | Flow().run(Init)
10 | }
11 | }
12 |
13 | fun main(args: Array) {
14 | Skill.main(args)
15 | }
16 |
--------------------------------------------------------------------------------
/JokeBot/src/main/kotlin/furhatos/app/jokebot/nlu/nlu.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.jokebot.nlu
2 |
3 | import furhatos.nlu.Intent
4 | import furhatos.util.Language
5 |
6 | class GoodJoke: Intent() {
7 | override fun getExamples(lang: Language): List {
8 | return listOf(
9 | "Good one", "I like it", "good joke", "very funny", "hilarious", "haha", "great joke", "amazing"
10 | )
11 | }
12 | }
13 |
14 | class BadJoke: Intent() {
15 | override fun getExamples(lang: Language): List {
16 | return listOf(
17 | "not funny", "not that funny", "not good", "not so good", "bad joke", "terrible"
18 | )
19 | }
20 | }
--------------------------------------------------------------------------------
/JokeBot/src/main/kotlin/furhatos/app/jokebot/setting/engagementParams.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.jokebot.setting
2 |
3 | val maxNumberOfUsers = 2
4 | val distanceToEngage = 1.0
--------------------------------------------------------------------------------
/JokeBot/src/main/kotlin/furhatos/app/jokebot/setting/personas.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.jokebot.setting
2 |
3 | import furhatos.flow.kotlin.FlowControlRunner
4 | import furhatos.flow.kotlin.furhat
5 | import furhatos.flow.kotlin.voice.PollyNeuralVoice
6 | import furhatos.flow.kotlin.voice.Voice
7 |
8 | class Persona(val name: String, val mask: String = "adult", val face: List, val voice: List) {
9 | }
10 |
11 | fun FlowControlRunner.activate(persona: Persona) {
12 | for (voice in persona.voice) {
13 | if (voice.isAvailable) {
14 | furhat.voice = voice
15 | break
16 | }
17 | }
18 |
19 | for (face in persona.face) {
20 | if (furhat.faces.get(persona.mask)?.contains(face)!!){
21 | furhat.setCharacter(face)
22 | break
23 | }
24 | }
25 | }
26 |
27 | val mainPersona = Persona(
28 | name = "Host",
29 | face = listOf("Alex"),
30 | voice = listOf(PollyNeuralVoice.Matthew(),PollyNeuralVoice.Joanna()).shuffled() // randomize what voice to select
31 | )
--------------------------------------------------------------------------------
/JokeBot/src/main/kotlin/furhatos/app/jokebot/util/helpers.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.jokebot.util
2 |
3 | /**
4 | * Calculates a score based on time spent laughing, and if the user said if it was a good/bad joke.
5 | * @param timeSpentLaughing The amount in ms that the user spent with a smile.
6 | * @param maxTimeLaughing The amount in ms that the user could have spent smiling.
7 | * @param badJoke Boolean depicting if the the user said it was a bad joke.
8 | * @param goodJoke Boolean depicting if the user said it was a good joke.
9 | * @return value between 0.75 and -0.5 depicting how well the joke worked.
10 | */
11 | fun calculateJokeScore(timeSpentLaughing: Long, maxTimeLaughing: Int, badJoke: Boolean, goodJoke: Boolean): Double {
12 | val smileModifier = when {
13 | badJoke -> -0.25
14 | goodJoke -> 0.25
15 | else -> 0.0
16 | }
17 |
18 | val gestureModifier = if (timeSpentLaughing != 0L) {
19 | (maxTimeLaughing / timeSpentLaughing) * 0.5
20 | } else {
21 | -0.25
22 | }
23 |
24 | return gestureModifier + smileModifier
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Furhat Robotics
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/OpenAIChat/README.md:
--------------------------------------------------------------------------------
1 | # OpenAIChat
2 |
3 | ## Description
4 |
5 | Example skill to showcase the integration with Large Language Models (LLMs) and Furhat.
6 |
7 | The skill uses the [Simple-OpenAI](https://github.com/sashirestela/simple-openai) library.
8 |
9 | ### Requirements:
10 |
11 | API key for OpenAI. Request it from https://openai.com/api/
12 |
13 | You need to set the key in openai.kt.
14 |
--------------------------------------------------------------------------------
/OpenAIChat/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/OpenAIChat/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/OpenAIChat/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-bin.zip
6 |
--------------------------------------------------------------------------------
/OpenAIChat/skill.properties:
--------------------------------------------------------------------------------
1 | name = OpenAIChat
2 | mainclass = furhatos.app.openaichat.OpenaichatSkill
3 | language = en-US
4 | logLevel = INFO
5 | version = 1.1.0
6 | #You may set this to a Furhat version.
7 | requiresVersion = false
8 | #Set to true if this skill should fail if there is no camera
9 | requiresCamera = false
10 | #Set to true if this skill should fail if there is no speaker
11 | requiresSpeaker = false
12 | #Set to true if this skill should fail if there is no microphone
13 | requiresMicrophone = false
14 | #Set to true if this skill should fail if there is no active recognizer
15 | requiresRecognizer = false
--------------------------------------------------------------------------------
/OpenAIChat/src/main/kotlin/furhatos/app/openaichat/flow/chatbot/chat.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.openaichat.flow.chatbot
2 |
3 | import furhatos.app.openaichat.flow.*
4 | import furhatos.app.openaichat.setting.activate
5 | import furhatos.app.openaichat.setting.hostPersona
6 | import furhatos.app.openaichat.setting.personas
7 | import furhatos.flow.kotlin.*
8 | import furhatos.nlu.common.No
9 | import furhatos.nlu.common.Yes
10 |
11 | val MainChat = state(Parent) {
12 | onEntry {
13 | activate(currentPersona)
14 | delay(2000)
15 | Furhat.dialogHistory.clear()
16 | furhat.say("Hello, I am ${currentPersona.fullDesc}. ${currentPersona.intro}")
17 | reentry()
18 | }
19 |
20 | onReentry {
21 | furhat.listen()
22 | }
23 |
24 | onResponse("can we stop", "goodbye") {
25 | furhat.say("Okay, goodbye")
26 | activate(hostPersona)
27 | delay(2000)
28 | furhat.say {
29 | random {
30 | +"I hope that was an insightful demonstration of generative A I and social robots"
31 | +"I hope you enjoyed conversing with a generative A I "
32 | +"I hope you found that interesting"
33 | }
34 | }
35 | goto(AfterChat)
36 | }
37 |
38 | onResponse {
39 | furhat.gesture(GazeAversion(2.0))
40 | val response = call {
41 | currentPersona.chatbot.getResponse()
42 | } as String
43 | furhat.say(response)
44 | reentry()
45 | }
46 |
47 | onNoResponse {
48 | reentry()
49 | }
50 | }
51 |
52 | val AfterChat: State = state(Parent) {
53 |
54 | onEntry {
55 | furhat.ask("Would you like to talk to someone else?")
56 | }
57 |
58 | onPartialResponse {
59 | raise(it.secondaryIntent)
60 | }
61 |
62 | onResponse {
63 | goto(ChoosePersona())
64 | }
65 |
66 | onResponse {
67 | furhat.say("Okay, goodbye then")
68 | goto(Idle)
69 | }
70 |
71 | for (persona in personas) {
72 | onResponse(persona.intent) {
73 | furhat.say("Okay, I will let you talk to ${persona.name}")
74 | currentPersona = persona
75 | goto(MainChat)
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/OpenAIChat/src/main/kotlin/furhatos/app/openaichat/flow/chatbot/openai.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.openaichat.flow.chatbot
2 |
3 | import furhatos.flow.kotlin.DialogHistory
4 | import furhatos.flow.kotlin.Furhat
5 | import io.github.sashirestela.openai.SimpleOpenAI
6 | import io.github.sashirestela.openai.domain.chat.ChatMessage
7 | import io.github.sashirestela.openai.domain.chat.ChatRequest
8 |
9 | /** Open AI API Key **/
10 | val serviceKey = ""
11 |
12 | val openAI = SimpleOpenAI.builder()
13 | .apiKey(serviceKey)
14 | .build();
15 |
16 | class OpenAIChatbot(val systemPrompt: String) {
17 |
18 | fun getResponse(): String {
19 | val chatRequestBuilder = ChatRequest.builder()
20 | .model("gpt-4o-mini")
21 | .message(ChatMessage.SystemMessage.of(systemPrompt))
22 |
23 | Furhat.dialogHistory.all.takeLast(10).forEach {
24 | when (it) {
25 | is DialogHistory.ResponseItem -> {
26 | chatRequestBuilder.message(ChatMessage.UserMessage.of(it.response.text))
27 | }
28 | is DialogHistory.UtteranceItem -> {
29 | chatRequestBuilder.message(ChatMessage.AssistantMessage.of(it.toText()))
30 | }
31 | }
32 | }
33 |
34 | var futureChat = openAI.chatCompletions().create(chatRequestBuilder.build())
35 | var chatResponse = futureChat.join()
36 | return chatResponse.firstContent().toString()
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/OpenAIChat/src/main/kotlin/furhatos/app/openaichat/flow/init.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.openaichat.flow
2 |
3 | import furhatos.app.openaichat.flow.chatbot.serviceKey
4 | import furhatos.app.openaichat.setting.activate
5 | import furhatos.app.openaichat.setting.hostPersona
6 | import furhatos.flow.kotlin.State
7 | import furhatos.flow.kotlin.state
8 | import furhatos.flow.kotlin.users
9 |
10 | val Init: State = state() {
11 | init {
12 | /** Check API key for the OpenAI GPT3 language model has been set */
13 | if (serviceKey.isEmpty()) {
14 | println("Missing API key for OpenAI language model. ")
15 | exit()
16 | }
17 |
18 | /** Set the Persona */
19 | activate(hostPersona)
20 |
21 | /** start the interaction */
22 | goto(InitFlow)
23 | }
24 |
25 | }
26 |
27 | val InitFlow: State = state() {
28 | onEntry {
29 | when {
30 | users.hasAny() -> goto(ChoosePersona())
31 | !users.hasAny() -> goto(Idle)
32 | }
33 | }
34 |
35 | }
36 |
37 |
38 |
--------------------------------------------------------------------------------
/OpenAIChat/src/main/kotlin/furhatos/app/openaichat/flow/main/greeting.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.openaichat.flow
2 |
3 | import furhatos.app.openaichat.flow.chatbot.MainChat
4 | import furhatos.app.openaichat.setting.Persona
5 | import furhatos.app.openaichat.setting.hostPersona
6 | import furhatos.app.openaichat.setting.personas
7 | import furhatos.flow.kotlin.*
8 | import furhatos.records.Location
9 |
10 | var currentPersona: Persona = hostPersona
11 |
12 | fun ChoosePersona() = state(Parent) {
13 |
14 | fun FlowControlRunner.presentPersonas() {
15 | furhat.say("You can choose to speak to one of these characters:")
16 | for (persona in personas) {
17 | //activate(persona)
18 | delay(300)
19 | furhat.say(persona.fullDesc)
20 | delay(300)
21 | }
22 | //activate(hostPersona)
23 | reentry()
24 | }
25 |
26 | onEntry {
27 | furhat.attend(users.random)
28 | presentPersonas()
29 | }
30 |
31 | onReentry {
32 | furhat.ask("Who would you like to talk to?")
33 | }
34 |
35 | onResponse("can you present them again", "could you repeat") {
36 | presentPersonas()
37 | }
38 |
39 | for (persona in personas) {
40 | onResponse(persona.intent) {
41 | furhat.say {
42 | random {
43 | +"Okay, I will let you talk to ${persona.name}."
44 | +"Okay, let's have a chat with ${persona.name}."
45 | +"Sure, we can talk to ${persona.name}."
46 | }
47 | random {
48 | +"When you want to end the conversation, just say, goodbye."
49 | +"When it's time to end the conversation, just say, goodbye."
50 | }
51 | }
52 | currentPersona = persona
53 | goto(MainChat)
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/OpenAIChat/src/main/kotlin/furhatos/app/openaichat/flow/main/idle.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.openaichat.flow
2 |
3 | import furhatos.app.openaichat.setting.activate
4 | import furhatos.app.openaichat.setting.hostPersona
5 | import furhatos.nlu.common.*
6 | import furhatos.flow.kotlin.*
7 | import furhatos.records.Location
8 |
9 | val Idle : State = state {
10 | onEntry {
11 | activate(hostPersona)
12 | furhat.attendNobody()
13 | }
14 |
15 | onUserEnter {
16 | furhat.attend(it)
17 | goto(ChoosePersona())
18 | }
19 |
20 | }
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/OpenAIChat/src/main/kotlin/furhatos/app/openaichat/flow/parent.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.openaichat.flow
2 |
3 | import furhatos.event.actions.ActionLipSync
4 | import furhatos.flow.kotlin.*
5 |
6 |
7 | val Parent: State = state {
8 |
9 | onUserLeave(instant = true) {
10 | if (users.count > 0) {
11 | if (it == users.current) {
12 | furhat.attend(users.other)
13 | goto(Idle)
14 | } else {
15 | furhat.glance(it)
16 | }
17 | } else {
18 | goto(Idle)
19 | }
20 | }
21 |
22 | onUserEnter(instant = true) {
23 | furhat.glance(it)
24 | }
25 |
26 | /** Averts the eye gaze of the robot at appropriate times to avoid robot staring at the user */
27 | onEvent(instant=true) {
28 | var silences = it.phones.phones.dropWhile { it.name == "_s" }.dropLastWhile { it.name == "_s" }.filter { it.name == "_s" }.toMutableList()
29 | if (silences.isNotEmpty()) {
30 | runThread(true) {
31 | var last = 0.0f
32 | while (silences.isNotEmpty()) {
33 | val silence = silences.removeAt(0)
34 | val sleepTime = (silence.start - 0.2) - last
35 | val avertTime = 0.2 + (silence.end - silence.start)
36 | if (sleepTime > 0.0) {
37 | Thread.sleep((sleepTime * 1000.0).toLong())
38 | furhat.gesture(GazeAversion(avertTime))
39 | }
40 | last = silence.end
41 | }
42 | }
43 | }
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/OpenAIChat/src/main/kotlin/furhatos/app/openaichat/main.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.openaichat
2 |
3 | import furhatos.app.openaichat.flow.*
4 | import furhatos.skills.Skill
5 | import furhatos.flow.kotlin.*
6 | import furhatos.nlu.LogisticMultiIntentClassifier
7 |
8 | class OpenaichatSkill : Skill() {
9 | override fun start() {
10 | Flow().run(Init)
11 | }
12 | }
13 |
14 | fun main(args: Array) {
15 | LogisticMultiIntentClassifier.setAsDefault()
16 | Skill.main(args)
17 | }
18 |
--------------------------------------------------------------------------------
/OpenAIChat/src/main/kotlin/furhatos/app/openaichat/setting/persona.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.openaichat.setting
2 |
3 | import furhatos.app.openaichat.flow.chatbot.OpenAIChatbot
4 | import furhatos.flow.kotlin.FlowControlRunner
5 | import furhatos.flow.kotlin.furhat
6 | import furhatos.flow.kotlin.voice.AcapelaVoice
7 | import furhatos.flow.kotlin.voice.PollyNeuralVoice
8 | import furhatos.flow.kotlin.voice.Voice
9 | import furhatos.nlu.SimpleIntent
10 |
11 | class Persona(
12 | val name: String,
13 | val otherNames: List = listOf(),
14 | val intro: String = "",
15 | val desc: String,
16 | val face: List,
17 | val mask: String = "adult",
18 | val voice: Voice
19 | ) {
20 | val fullDesc = "$name, the $desc"
21 |
22 | val intent = SimpleIntent((listOf(name, desc, fullDesc) + otherNames))
23 |
24 | /** The prompt for the openAI language model **/
25 | val chatbot = OpenAIChatbot("You are $name, the $desc. You should speak in a conversational style. Never say more than two sentences.")
26 | }
27 |
28 | fun FlowControlRunner.activate(persona: Persona) {
29 | furhat.voice = persona.voice
30 |
31 | for (face in persona.face) {
32 | if (furhat.faces[persona.mask]?.contains(face)!!) {
33 | furhat.character = face
34 | break
35 | }
36 | }
37 | }
38 |
39 | val hostPersona = Persona(
40 | name = "Host",
41 | desc = "host",
42 | face = listOf("Alex", "default"),
43 | voice = PollyNeuralVoice("Matthew")
44 | )
45 |
46 | val personas = listOf(
47 | Persona(
48 | name = "Marvin",
49 | desc = "depressed robot",
50 | face = listOf("Titan"),
51 | voice = PollyNeuralVoice("Kimberly")
52 | ),
53 | Persona(
54 | name = "Emma",
55 | desc = "personal trainer",
56 | intro = "How do you think I could help you?",
57 | face = listOf("Isabel"),
58 | voice = PollyNeuralVoice("Olivia")
59 | ),
60 | Persona(
61 | name = "Albert Einstein",
62 | otherNames = listOf("Einstein", "Albert"),
63 | desc = "famous scientist",
64 | intro = "What can I help you with?",
65 | face = listOf("James"),
66 | voice = PollyNeuralVoice("Brian")
67 | )
68 | )
--------------------------------------------------------------------------------
/OpenAIChat/src/main/kotlin/furhatos/app/openaichat/setting/users.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.openaichat.flow
2 |
3 |
--------------------------------------------------------------------------------
/OpenAIChat/src/main/kotlin/furhatos/app/openaichat/utils/utils.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.openaichat.flow
2 |
3 | import furhatos.flow.kotlin.*
4 | import furhatos.gestures.ARKitParams
5 | import furhatos.gestures.BasicParams
6 | import furhatos.gestures.defineGesture
7 | import kotlin.random.Random
8 |
9 |
10 | fun FlowControlRunner.askForAnything(text: String) {
11 |
12 | call(state {
13 | onEntry {
14 | furhat.ask(text)
15 | }
16 | onResponse {
17 | terminate()
18 | }
19 | })
20 |
21 | }
22 |
23 | val random = Random(0)
24 |
25 | fun GazeAversion(duration: Double = 1.0) = defineGesture("GazeAversion") {
26 | val dur = duration.coerceAtLeast(0.2)
27 | frame(0.05, dur-0.05) {
28 | when (random.nextInt(4)) {
29 | 0 -> {
30 | ARKitParams.EYE_LOOK_DOWN_LEFT to 0.5
31 | ARKitParams.EYE_LOOK_DOWN_RIGHT to 0.5
32 | ARKitParams.EYE_LOOK_OUT_LEFT to 0.5
33 | ARKitParams.EYE_LOOK_IN_RIGHT to 0.5
34 | }
35 | 1 -> {
36 | ARKitParams.EYE_LOOK_UP_LEFT to 0.5
37 | ARKitParams.EYE_LOOK_UP_RIGHT to 0.5
38 | ARKitParams.EYE_LOOK_OUT_LEFT to 0.5
39 | ARKitParams.EYE_LOOK_IN_RIGHT to 0.5
40 | }
41 | 2 -> {
42 | ARKitParams.EYE_LOOK_DOWN_LEFT to 0.5
43 | ARKitParams.EYE_LOOK_DOWN_RIGHT to 0.5
44 | ARKitParams.EYE_LOOK_OUT_RIGHT to 0.5
45 | ARKitParams.EYE_LOOK_IN_LEFT to 0.5
46 | }
47 | else -> {
48 | ARKitParams.EYE_LOOK_UP_LEFT to 0.5
49 | ARKitParams.EYE_LOOK_UP_RIGHT to 0.5
50 | ARKitParams.EYE_LOOK_OUT_RIGHT to 0.5
51 | ARKitParams.EYE_LOOK_IN_LEFT to 0.5
52 | }
53 | }
54 | }
55 | reset(dur)
56 | }
57 |
--------------------------------------------------------------------------------
/Quiz/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | /out
3 | /build
4 | build/
5 | /.gradle
6 | /logs
7 | /.idea
8 | save_flow.xml
9 | *.iml
10 |
--------------------------------------------------------------------------------
/Quiz/README.md:
--------------------------------------------------------------------------------
1 | # Quiz
2 | Compete with a friend in a quiz game
3 |
4 | ## Description
5 | Partake in a quiz game with the robot where it will ask you and your friends multiple choice questions and receive a
6 | score for every correct answer.
7 |
8 | ## Usage
9 | Max number of users is set to: 2
10 | No other specific requirements.
--------------------------------------------------------------------------------
/Quiz/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | plugins {
3 | id "org.jetbrains.kotlin.jvm" version "1.8.21"
4 | id 'com.github.johnrengelman.shadow' version '2.0.4'
5 | }
6 |
7 | apply plugin: 'java'
8 | apply plugin: 'kotlin'
9 |
10 | //Defines what version of Java to use.
11 | sourceCompatibility = 1.8
12 |
13 | //Defines how Kotlin should compile.
14 | compileKotlin {
15 | sourceCompatibility = JavaVersion.VERSION_1_8
16 | targetCompatibility = JavaVersion.VERSION_1_8
17 |
18 | kotlinOptions {
19 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
20 | jvmTarget = "1.8"
21 | apiVersion = "1.8"
22 | languageVersion = "1.8"
23 | }
24 | }
25 |
26 | //Defines how Kotlin should compile when testingTry to keep it the same as compileKotlin.
27 | compileTestKotlin {
28 | sourceCompatibility = JavaVersion.VERSION_1_8
29 | targetCompatibility = JavaVersion.VERSION_1_8
30 |
31 | kotlinOptions {
32 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
33 | jvmTarget = "1.8"
34 | apiVersion = "1.8"
35 | languageVersion = "1.8"
36 | }
37 | }
38 |
39 | repositories {
40 | mavenLocal()
41 | mavenCentral()
42 | maven { url "https://s3-eu-west-1.amazonaws.com/furhat-maven/releases"}
43 | maven { url 'https://repo.gradle.org/gradle/libs-releases' }
44 | }
45 |
46 | dependencies {
47 | implementation 'com.furhatrobotics.furhatos:furhat-commons:2.7.0'
48 | }
49 |
50 | //These new blocks are needed to package your project into a working skill file.
51 | jar {
52 | def lowerCasedName = baseName.toLowerCase()
53 | def normalizedName = lowerCasedName.substring(0,1).toUpperCase() + lowerCasedName.substring(1)
54 | manifest.attributes(
55 | 'Class-Path': configurations.compileClasspath.collect { it.getName() }.join(' '),
56 | 'Main-Class': "furhatos.app.${lowerCasedName}.${normalizedName}Skill"
57 | )
58 | }
59 |
60 | shadowJar {
61 | manifest {
62 | exclude '**/Log4j2Plugins.dat'
63 | exclude '**/node_modules'
64 | }
65 | from "skill.properties"
66 | from "assets"
67 | extension 'skill'
68 | Properties properties = new Properties()
69 | properties.load(project.file('skill.properties').newDataInputStream())
70 | def version = properties.getProperty('version')
71 | def name = properties.getProperty('name')
72 | archiveName = "${name}_${version}.skill"
73 | }
74 |
--------------------------------------------------------------------------------
/Quiz/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/Quiz/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Quiz/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jul 03 17:47:58 CEST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-all.zip
7 |
--------------------------------------------------------------------------------
/Quiz/skill.properties:
--------------------------------------------------------------------------------
1 | mainclass=Quiz
2 | version=1.0.0
3 | package=furhatos.app.quiz
4 | name=Quiz
5 | logLevel = INFO
--------------------------------------------------------------------------------
/Quiz/src/main/kotlin/furhatos/app/quiz/flow/init.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.quiz.flow
2 |
3 | import furhatos.app.quiz.flow.main.Idle
4 | import furhatos.app.quiz.setting.activate
5 | import furhatos.app.quiz.setting.distanceToEngage
6 | import furhatos.app.quiz.setting.maxNumberOfUsers
7 | import furhatos.app.quiz.setting.quizPersona
8 | import furhatos.flow.kotlin.State
9 | import furhatos.flow.kotlin.furhat
10 | import furhatos.flow.kotlin.state
11 | import furhatos.flow.kotlin.users
12 | import furhatos.flow.kotlin.voice.PollyNeuralVoice
13 | import furhatos.flow.kotlin.voice.PollyVoice
14 | import furhatos.util.Gender
15 | import furhatos.util.Language
16 |
17 | val Init: State = state() {
18 | init {
19 | /** Set our default interaction parameters
20 | By default, only two users can be actively engaged at the same time.
21 | By setting an engagement policy, the innerDistance (triggering entry),
22 | outerDistance (triggering exit), and maximum number of concurrent users
23 | can be set to values applicable to the skill's use case.
24 | */
25 | users.setSimpleEngagementPolicy(distanceToEngage, maxNumberOfUsers)
26 |
27 | /** Set our main character - defined in personas */
28 | activate(quizPersona)
29 |
30 | /** start the interaction */
31 | goto(Idle)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Quiz/src/main/kotlin/furhatos/app/quiz/flow/main/newGame.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.quiz.flow.main
2 |
3 | import furhatos.app.quiz.flow.Parent
4 | import furhatos.app.quiz.questions.QuestionSet
5 | import furhatos.app.quiz.setting.playing
6 | import furhatos.flow.kotlin.furhat
7 | import furhatos.flow.kotlin.state
8 | import furhatos.flow.kotlin.users
9 |
10 | val NewGame = state(parent = Parent) {
11 | onEntry {
12 | playing = true
13 | rounds = 0
14 |
15 | furhat.say("I will ask you $maxRounds multiple choice questions. And we'll see how many points you can get. ")
16 | if (users.count > 1) {
17 | furhat.say("If you answer wrong, the question will go over to the next person")
18 | }
19 |
20 | furhat.say("Alright, here we go!")
21 | QuestionSet.next()
22 | furhat.attend(users.playing().first())
23 | goto(AskQuestion)
24 | }
25 | }
--------------------------------------------------------------------------------
/Quiz/src/main/kotlin/furhatos/app/quiz/flow/main/start.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.quiz.flow.main
2 |
3 | import furhatos.app.quiz.AnswerOption
4 | import furhatos.app.quiz.DontKnow
5 | import furhatos.app.quiz.RequestRepeatOptions
6 | import furhatos.app.quiz.RequestRepeatQuestion
7 | import furhatos.app.quiz.flow.Parent
8 | import furhatos.app.quiz.questions.QuestionSet
9 | import furhatos.app.quiz.setting.nextPlaying
10 | import furhatos.app.quiz.setting.notQuestioned
11 | import furhatos.app.quiz.setting.playing
12 | import furhatos.app.quiz.setting.quiz
13 | import furhatos.flow.kotlin.*
14 | import furhatos.gestures.Gestures
15 | import furhatos.nlu.common.No
16 | import furhatos.nlu.common.RequestRepeat
17 | import furhatos.nlu.common.Yes
18 | import furhatos.records.User
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Quiz/src/main/kotlin/furhatos/app/quiz/flow/parent.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.quiz.flow
2 |
3 | import furhatos.app.quiz.RequestRules
4 | import furhatos.app.quiz.flow.main.Idle
5 | import furhatos.app.quiz.flow.main.NewQuestion
6 | import furhatos.app.quiz.flow.main.maxRounds
7 | import furhatos.app.quiz.setting.nextPlaying
8 | import furhatos.app.quiz.setting.playing
9 | import furhatos.app.quiz.setting.quiz
10 | import furhatos.flow.kotlin.*
11 | import furhatos.nlu.common.Goodbye
12 |
13 | val Parent: State = state {
14 |
15 | onResponse {
16 | furhat.say("You get $maxRounds questions, each with 4 options. You get one point for each correct answer.")
17 | if (users.count > 1) {
18 | furhat.say("If you answer wrong, the question will go over to the next person")
19 | }
20 | reentry()
21 | }
22 |
23 | onResponse {
24 | furhat.say("It was nice talking to you. Goodbye")
25 | goto(Idle)
26 | }
27 |
28 | onUserEnter(instant = true) {
29 | furhat.glance(it.id)
30 | }
31 |
32 | onUserLeave(instant = true) {
33 | it.quiz.playing = false
34 | if (users.current?.id == it.id) {
35 | furhat.stopSpeaking()
36 | if (users.playing().count() > 0) {
37 | furhat.attend(users.nextPlaying())
38 | goto(NewQuestion)
39 | } else {
40 | goto(Idle)
41 | }
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/Quiz/src/main/kotlin/furhatos/app/quiz/main.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.quiz
2 |
3 | import furhatos.app.quiz.flow.Init
4 | import furhatos.app.quiz.flow.main.Idle
5 | import furhatos.flow.kotlin.Flow
6 | import furhatos.skills.Skill
7 |
8 | class Quiz : Skill() {
9 | override fun start() {
10 | Flow().run(Init)
11 | }
12 | }
13 |
14 | fun main(args: Array) {
15 | Skill.main(args)
16 | }
17 |
--------------------------------------------------------------------------------
/Quiz/src/main/kotlin/furhatos/app/quiz/nlu.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.quiz
2 |
3 | import furhatos.app.quiz.questions.QuestionSet
4 | import furhatos.nlu.EnumEntity
5 | import furhatos.nlu.EnumItem
6 | import furhatos.nlu.Intent
7 | import furhatos.util.Language
8 |
9 | class DontKnow : Intent() {
10 | override fun getExamples(lang: Language): List {
11 | return listOf(
12 | "I don't know",
13 | "don't know",
14 | "no idea",
15 | "I have no idea"
16 | )
17 | }
18 | }
19 |
20 | class RequestRules : Intent() {
21 | override fun getExamples(lang: Language): List {
22 | return listOf(
23 | "what are the rules",
24 | "how does it work"
25 | )
26 | }
27 | }
28 |
29 | class RequestRepeatQuestion : Intent() {
30 | override fun getExamples(lang: Language): List {
31 | return listOf(
32 | "what was the question",
33 | "can you repeat the question",
34 | "what was the question again"
35 | )
36 | }
37 | }
38 |
39 | class RequestRepeatOptions : Intent() {
40 | override fun getExamples(lang: Language): List {
41 | return listOf(
42 | "what are the options",
43 | "can you repeat the options",
44 | "what was the options"
45 | )
46 | }
47 | }
48 |
49 | class AnswerOption : EnumEntity {
50 |
51 | var correct : Boolean = false
52 |
53 | // Every entity and intent needs an empty constructor.
54 | constructor() {
55 | }
56 |
57 | // Since we are overwriting the value, we need to use this custom constructor
58 | constructor(correct : Boolean, value : String) {
59 | this.correct = correct
60 | this.value = value
61 | }
62 |
63 | override fun getEnumItems(lang: Language): List {
64 | return QuestionSet.current.options;
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/Quiz/src/main/kotlin/furhatos/app/quiz/setting/engagementParams.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.quiz.setting
2 |
3 | import furhatos.flow.kotlin.voice.PollyVoice
4 | import furhatos.util.Gender
5 | import furhatos.util.Language
6 |
7 | val maxNumberOfUsers = 4
8 | val distanceToEngage = 1.5
--------------------------------------------------------------------------------
/Quiz/src/main/kotlin/furhatos/app/quiz/setting/personas.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.quiz.setting
2 |
3 | import furhatos.flow.kotlin.FlowControlRunner
4 | import furhatos.flow.kotlin.furhat
5 | import furhatos.flow.kotlin.voice.PollyNeuralVoice
6 | import furhatos.flow.kotlin.voice.Voice
7 |
8 | class Persona(val name: String, val mask: String = "adult", val face: List, val voice: List) {
9 | }
10 |
11 | fun FlowControlRunner.activate(persona: Persona) {
12 | for (voice in persona.voice) {
13 | if (voice.isAvailable) {
14 | furhat.voice = voice
15 | break
16 | }
17 | }
18 | for (face in persona.face) {
19 | if (furhat.faces.get(persona.mask)?.contains(face)!!){
20 | furhat.setCharacter(face)
21 | break
22 | }
23 | }
24 | }
25 |
26 | val quizPersona = Persona(
27 | name = "Quizmaster",
28 | face = listOf("Alex", "default"),
29 | voice = listOf(PollyNeuralVoice.Joanna())
30 | )
--------------------------------------------------------------------------------
/Quiz/src/main/kotlin/furhatos/app/quiz/setting/users.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.quiz.setting
2 |
3 | import furhatos.records.Record
4 | import furhatos.records.User
5 | import furhatos.skills.UserManager
6 |
7 | // User variables
8 | class SkillData(
9 | var score : Int = 0,
10 | var lastScore : Int = 0,
11 | var interested : Boolean = true,
12 | var playing: Boolean = false,
13 | var played : Boolean = false,
14 | var questionsAsked : MutableList = mutableListOf()
15 | ) : Record()
16 |
17 | val User.quiz : SkillData
18 | get() = data.getOrPut(SkillData::class.qualifiedName, SkillData())
19 |
20 | // Custom user getters for convenience
21 | fun UserManager.interested() = list.filter {
22 | it.quiz.interested && !it.quiz.playing
23 | }
24 |
25 | fun UserManager.playing() = list.filter {
26 | it.quiz.playing
27 | }
28 |
29 | fun UserManager.notQuestioned(question: String) = list.filter {
30 | it.quiz.playing && !it.quiz.questionsAsked.contains(question)
31 | }
32 |
33 | fun UserManager.nextPlaying() : User {
34 | val nextPlayer = list.filter {
35 | it.quiz.playing && it != current
36 | }.first()
37 | if (nextPlayer == null) {
38 | return current
39 | }
40 | else {
41 | return nextPlayer
42 | }
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Furhat example-skills 2nd generation.
2 |
3 | Example skills for the Furhat robot, provided by Furhat Robotics. Contributions welcome from everyone, just send a PR!
4 |
5 | # Dependencies
6 |
7 | * A running Furhat SDK [See furhat.io](https://furhat.io)
8 | * (For the FurGUI): Node.js version >= 7.2.0 and npm >= 3.10.9
9 |
10 | # Contents
11 |
12 | Skill | Description | Concepts showcased
13 | ----------------------|---------------------------------------------|------------------------------------------------------
14 | Interviewee | A fully wizarded skill where Furhat is getting interviewed by a journalist | Wizarding
15 | JokeBot | A skill for the robot (does not work on SDK) that tells jokes and uses the user's reaction to assert if it is a good joke. | Gesture Detection, Storage of interaction data, Randomizing interactions
16 | Quiz | An example of a quiz skill | Wizarding
17 | demo-skill | A skill to demo Furhat | Wizarding
18 | CustomASR | How to implement a custom ASR and use extension functions | Audio Feed, Using 3rd party ASR
19 | FortuneTeller | A robot telling people their fortune | Demo of interaction that works well in noisy environments.
20 | OpenAIAchat | talk to characters powered by GPT-3 | showcase integration with a Large Language Model
21 |
22 | See also skills examples from the community!
23 | Skill | By | Link
24 | ----------------------|---------------------------------------------|------------------------------------------------------
25 | SaharaInterview | Autoura | https://github.com/Autoura/SahraInterview
26 |
27 | # Running skills
28 | 1. Clone the repository, `git clone https://github.com/FurhatRobotics/example-skills`
29 | 2. In IntelliJ IDEA, import each individual skill as a new gradle project (or module if you have an existing project) (use the _from existing source_ alternative and select the _build.gradle_ file)
30 | 3. Make sure you have the Furhat SDK development server or a robot running.
31 | 4. If the skill has a GUI (currently only the FurGUI skill), run `npm install` in the GUI root folder
32 | 5. Run the skill by the main method in the skill's `main.kt` file. If you want to run on a robot, see [this part of the docs](https://docs.furhat.io/skills/#running-a-skill-on-a-robot)
33 |
34 | # Documentation
35 | For more info, see [docs.furhat.io](https://docs.furhat.io).
36 |
--------------------------------------------------------------------------------
/demo-skill/LICENSE:
--------------------------------------------------------------------------------
1 | You may only use the code in this repository if you have approved the terms and conditions for the Furhat SDK by signing up at https://furhat.io. The terms of use for the Furhat SDK also governs this skill.
2 |
3 | In other words, you may use this skill if you are a registered developer at furhat.io and only for use with the Furhat robot or the virtual Furhat simulator.
4 |
5 | Contact us if you have any questions on the above!
6 |
--------------------------------------------------------------------------------
/demo-skill/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "org.jetbrains.kotlin.jvm" version "1.8.21"
3 | id 'com.github.johnrengelman.shadow' version '2.0.4'
4 | }
5 |
6 | apply plugin: 'java'
7 | apply plugin: 'kotlin'
8 | version = "1.1.3"
9 |
10 | //Defines what version of Java to use.
11 | sourceCompatibility = 1.8
12 |
13 | //Defines how Kotlin should compile.
14 | compileKotlin {
15 | sourceCompatibility = JavaVersion.VERSION_1_8
16 | targetCompatibility = JavaVersion.VERSION_1_8
17 |
18 | kotlinOptions {
19 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
20 | jvmTarget = "1.8"
21 | apiVersion = "1.8"
22 | languageVersion = "1.8"
23 | }
24 | }
25 | //Defines how Kotlin should compile when testingTry to keep it the same as compileKotlin.
26 | compileTestKotlin {
27 | sourceCompatibility = JavaVersion.VERSION_1_8
28 | targetCompatibility = JavaVersion.VERSION_1_8
29 |
30 | kotlinOptions {
31 | //Defines what jvm bytecode to use, 1.8 rather than 1.6
32 | jvmTarget = "1.8"
33 | apiVersion = "1.8"
34 | languageVersion = "1.8"
35 | }
36 | }
37 |
38 | repositories {
39 | mavenLocal()
40 | mavenCentral()
41 | maven { url "https://s3-eu-west-1.amazonaws.com/furhat-maven/releases"}
42 | maven { url 'https://repo.gradle.org/gradle/libs-releases' }
43 | }
44 |
45 | dependencies {
46 | implementation 'com.furhatrobotics.furhatos:furhat-commons:2.7.0'
47 | }
48 |
49 | jar {
50 | def lowerCasedName = baseName.toLowerCase()
51 | def normalizedName = lowerCasedName.substring(0,1).toUpperCase() + lowerCasedName.substring(1)
52 | manifest.attributes(
53 | 'Class-Path': configurations.compileClasspath.collect { it.getName() }.join(' '),
54 | 'Main-Class': "furhatos.app.${lowerCasedName}.${normalizedName}Skill"
55 | )
56 | }
57 |
58 | //ShadowJar depends on jar being finished properly.
59 | shadowJar {
60 | manifest {
61 | exclude '**/Log4j2Plugins.dat'
62 | exclude '**/node_modules'
63 | }
64 | from "skill.properties"
65 | from "assets"
66 | extension 'skill'
67 | }
--------------------------------------------------------------------------------
/demo-skill/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FurhatRobotics/example-skills/cf48ea35bcffe8e1e0865a726fd2d525eaf35508/demo-skill/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/demo-skill/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-bin.zip
6 |
--------------------------------------------------------------------------------
/demo-skill/skill.properties:
--------------------------------------------------------------------------------
1 | name = Demo
2 | mainclass = furhatos.app.demo.DemoSkill
3 | language = en-US
4 | logLevel = INFO
5 | #You may set this to a Furhat version.
6 | requiresVersion = false
7 | #Set to true if this skill should fail if there is no camera
8 | requiresCamera = false
9 | #Set to true if this skill should fail if there is no speaker
10 | requiresSpeaker = false
11 | #Set to true if this skill should fail if there is no microphone
12 | requiresMicrophone = false
13 | #Set to true if this skill should fail if there is no active recognizer
14 | requiresRecognizer = false
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/actions/chat.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow.actions
2 |
3 | import furhatos.app.chitchat.snippets.chatSnippets
4 | import furhatos.app.demo.flow.Active
5 | import furhatos.app.demo.flow.wizardButtons
6 | import furhatos.app.demo.nlu.ExitIntent
7 | import furhatos.app.demo.util.AttendUsers
8 | import furhatos.flow.kotlin.furhat
9 | import furhatos.flow.kotlin.onResponse
10 | import furhatos.flow.kotlin.state
11 | import furhatos.snippets.SnippetRunner
12 | import furhatos.snippets.SnippetState
13 | import furhatos.snippets.TakeInitiative
14 | import furhatos.snippets.TerminateSnippetState
15 |
16 | val snippetRunner = SnippetRunner(chatSnippets)
17 |
18 |
19 | val ChatState = state(SnippetState(snippetRunner)) {
20 | val exit = "EXIT"
21 |
22 | include(wizardButtons)
23 |
24 | onEntry {
25 | send(AttendUsers(shouldAlterAttentionOnSpeech = true))
26 | raise(TakeInitiative())
27 | }
28 |
29 |
30 | onResponse {
31 | raise(exit)
32 | }
33 |
34 | onEvent {
35 | raise(exit)
36 | }
37 |
38 | onEvent(exit) {
39 | furhat.say("Okay, let's stop chatting")
40 | goto(Active(returning = true))
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/actions/parrot.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow
2 |
3 | import furhatos.app.demo.nlu.ExitIntent
4 | import furhatos.app.demo.personas.phrases
5 | import furhatos.flow.kotlin.furhat
6 | import furhatos.flow.kotlin.onResponse
7 | import furhatos.flow.kotlin.state
8 |
9 | val ParrotMode = state(Active()) {
10 | onEntry {
11 | furhat.ask(phrases.parrotStart)
12 | }
13 |
14 | onResponse {
15 | furhat.say(phrases.okayIWillStop)
16 | terminate()
17 | }
18 |
19 | onResponse {
20 | furhat.ask(it.text)
21 | }
22 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/actions/presentation.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow
2 |
3 | import furhatos.app.demo.flow.modes.Parent
4 | import furhatos.app.demo.personas.Persona
5 | import furhatos.app.demo.personas.phrases
6 | import furhatos.flow.kotlin.furhat
7 | import furhatos.flow.kotlin.state
8 | import furhatos.flow.kotlin.voice.AcapelaVoice
9 | import furhatos.gestures.Gestures
10 |
11 | val Presentation = state(Parent) {
12 |
13 | onEntry {
14 | val persona = Persona.active
15 |
16 | if (persona.voice is AcapelaVoice) {
17 | furhat.say("#THROAT01#")
18 | }
19 |
20 | phrases.presentation.forEach {
21 | furhat.say(it)
22 | delay(100)
23 | }
24 |
25 | furhat.gesture(Gestures.BigSmile)
26 |
27 | delay(1500)
28 |
29 | terminate()
30 | }
31 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/autoBehavior/attendingLocation.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow.autoBehavior
2 |
3 | import furhatos.app.demo.MICROMOVEMENTS_INTERVAL
4 | import furhatos.app.demo.util.getRandomNearbyLocation
5 | import furhatos.flow.kotlin.State
6 | import furhatos.flow.kotlin.furhat
7 | import furhatos.flow.kotlin.state
8 | import furhatos.records.Location
9 | import furhatos.records.User
10 |
11 | fun attendingLocation(location: Location = User.NOBODY.head.location, randomMovements : Boolean = true) : State = state(autoBehavior()) {
12 | onEntry {
13 | furhat.attend(location)
14 | }
15 |
16 | onTime(repeat = MICROMOVEMENTS_INTERVAL, instant = true) {
17 | if (randomMovements) {
18 | furhat.attend(location.getRandomNearbyLocation(0.1))
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/autoBehavior/attendingUsers.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow.autoBehavior
2 |
3 | import furhatos.app.demo.DEFAULT_LOCATION
4 | import furhatos.app.demo.MICROMOVEMENTS_INTERVAL
5 | import furhatos.app.demo.util.closest
6 | import furhatos.app.demo.util.getRandomNearbyLocation
7 | import furhatos.event.monitors.MonitorSpeechEnd
8 | import furhatos.event.senses.SenseSpeechStart
9 | import furhatos.flow.kotlin.State
10 | import furhatos.flow.kotlin.furhat
11 | import furhatos.flow.kotlin.state
12 | import furhatos.flow.kotlin.users
13 | import java.util.*
14 |
15 | fun attendingUsers(shouldToggleAttention: Boolean = true) : State = state(autoBehavior()) {
16 | onEntry {
17 | if (users.count > 0) {
18 | furhat.attend(users.closest)
19 | }
20 | }
21 |
22 | onTime(repeat = MICROMOVEMENTS_INTERVAL, instant = true) {
23 | // This will make sure Furhat does some random micromovements with his head while attending
24 | furhat.attend(users.current.head.location.getRandomNearbyLocation(0.03))
25 | }
26 |
27 | onEvent {
28 | // 66% likelihood to, after Furhat has spoken, change attention to another
29 | // user if we have one, to get some variation.
30 | if (users.count > 1 && shouldToggleAttention && Random().nextInt(3) != 2) {
31 | furhat.attend(users.other)
32 | }
33 | }
34 |
35 | onEvent {
36 | when {
37 | users.count > 0 -> furhat.attend(users.closest)
38 | else -> furhat.attend(DEFAULT_LOCATION)
39 | }
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/autoBehavior/autoBehavior.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow.autoBehavior
2 |
3 | import furhatos.app.demo.util.*
4 | import furhatos.flow.kotlin.State
5 | import furhatos.flow.kotlin.furhat
6 | import furhatos.flow.kotlin.state
7 |
8 | fun autoBehavior(shouldReset : Boolean = true) : State = state {
9 | onEntry {
10 | if (shouldReset) {
11 | furhat.attendNobody()
12 | }
13 | }
14 |
15 | onEvent {
16 | goto(attendingLocation(randomMovements = it.randomMovements))
17 | }
18 |
19 | onEvent {
20 | goto(autoBehavior(shouldReset = false))
21 | }
22 |
23 | onEvent {
24 | goto(lookingAround())
25 | }
26 |
27 | onEvent {
28 | goto(attendingUsers(it.shouldAlterAttentionOnSpeech))
29 | }
30 |
31 | onEvent {
32 | goto(attendingLocation(location = it.location))
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/autoBehavior/lookingAround.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow.autoBehavior
2 |
3 | import furhatos.app.demo.LOOKAROUND_INTERVAL
4 | import furhatos.app.demo.util.getRandomNearbyLocation
5 | import furhatos.flow.kotlin.State
6 | import furhatos.flow.kotlin.furhat
7 | import furhatos.flow.kotlin.state
8 | import furhatos.flow.kotlin.users
9 | import furhatos.records.Location
10 |
11 | fun lookingAround(startingLocation: Location = Location(0.0, 0.0, 1.5)) : State = state {
12 | onEntry {
13 | furhat.attend(startingLocation)
14 | }
15 |
16 | onTime(repeat = LOOKAROUND_INTERVAL, instant = true) {
17 | val userAttendActions = users.list.map {
18 | { furhat.attend(it) }
19 | }.toTypedArray()
20 |
21 | random(
22 | *userAttendActions,
23 | { furhat.attend(startingLocation.getRandomNearbyLocation(0.1)) }
24 | )
25 | }
26 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/modes/init.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow
2 |
3 | import furhatos.app.demo.PRELOAD_WIKIDATA_ENTITIES
4 | import furhatos.app.demo.personas.Persona
5 | import furhatos.app.demo.personas.setPersona
6 | import furhatos.flow.kotlin.furhat
7 | import furhatos.flow.kotlin.state
8 | import furhatos.flow.kotlin.users
9 | import furhatos.nlu.wikidata.City
10 | import furhatos.nlu.wikidata.Film
11 | import furhatos.nlu.wikidata.MusicArtist
12 | import furhatos.nlu.wikidata.MusicGenre
13 | import furhatos.util.Language
14 |
15 | val init = state {
16 | // Currently using a button as an indicator for the operator ;-)
17 | if (PRELOAD_WIKIDATA_ENTITIES) {
18 | onButton("Preloading intents, hold on") {}
19 | }
20 | else {
21 | onButton("Loading ...") {}
22 | }
23 |
24 | onEntry {
25 | /* The WikiData entities used in chat are quite large and thus you
26 | probably don't want to preload them during development iterations,
27 | unless you are working on the chit chat.
28 | */
29 | if (PRELOAD_WIKIDATA_ENTITIES) {
30 | val lang = Language.ENGLISH_US
31 | City().preload(lang)
32 | Film().preload(lang)
33 | MusicArtist().preload(lang)
34 | MusicGenre().preload(lang)
35 | }
36 |
37 | users.setSimpleEngagementPolicy(1.2, 1.7, 3)
38 | furhat.setPersona(Persona.list.first(), initial = true)
39 | goto(Sleeping)
40 | }
41 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/modes/parent.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow.modes
2 |
3 | import furhatos.app.demo.flow.partials.conversationalHandlers
4 | import furhatos.app.demo.flow.partials.functionalHandlers
5 | import furhatos.app.demo.flow.wizardButtons
6 | import furhatos.flow.kotlin.State
7 | import furhatos.flow.kotlin.furhat
8 | import furhatos.flow.kotlin.onResponse
9 | import furhatos.flow.kotlin.state
10 | import furhatos.gestures.Gestures
11 |
12 | val Parent : State = state {
13 | // New include syntax for partial states, see https://docs.furhat.io/flow/#partial-states-and-extending-states
14 | include(wizardButtons)
15 | include(functionalHandlers)
16 | include(conversationalHandlers)
17 |
18 | // Silent fallback since we don't want to use the build-in fallbacks, see https://docs.furhat.io/listening/#changing-default-responses
19 | onResponse {
20 | furhat.gesture(Gestures.BrowRaise)
21 | furhat.listen()
22 | }
23 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/modes/passive.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow.modes
2 |
3 | import furhatos.app.demo.flow.Active
4 | import furhatos.app.demo.nlu.ConversationalIntent
5 | import furhatos.app.demo.util.LookAround
6 | import furhatos.flow.kotlin.*
7 | import furhatos.nlu.NullIntent
8 |
9 | // State where Furhat is awake but passive on a listen loop
10 | val Passive : State = state(Parent) {
11 | onEntry {
12 | send(LookAround())
13 | furhat.listen()
14 | }
15 |
16 | onReentry {
17 | furhat.listen()
18 | }
19 |
20 | onNoResponse {
21 | furhat.listen()
22 | }
23 |
24 | onResponse(cond = { it.intent == NullIntent }) {
25 | furhat.listen()
26 | }
27 |
28 | onResponse(cond = { it.intent is ConversationalIntent }) {
29 | goto(Active(it))
30 | }
31 | }
32 |
33 |
34 |
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/modes/sleeping.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow
2 |
3 | import furhatos.app.demo.flow.modes.Parent
4 | import furhatos.app.demo.nlu.ChatIntent
5 | import furhatos.app.demo.nlu.ConversationalIntent
6 | import furhatos.app.demo.nlu.WakeupIntent
7 | import furhatos.app.demo.util.LookStraight
8 | import furhatos.app.demo.util.setLED
9 | import furhatos.autobehavior.setDefaultMicroexpression
10 | import furhatos.flow.kotlin.*
11 | import furhatos.gestures.Gestures
12 | import furhatos.nlu.common.Greeting
13 |
14 | // State requiring the user to "wake up" Furhat
15 | val Sleeping = state(Parent) {
16 | onEntry {
17 | send(LookStraight())
18 | furhat.setLED("off")
19 | furhat.setDefaultMicroexpression(blinking = false, eyeMovements = false, facialMovements = false)
20 | furhat.gesture(Gestures.CloseEyes, priority = 1)
21 | furhat.stopSpeaking()
22 | furhat.listen()
23 | }
24 |
25 | onReentry {
26 | furhat.listen()
27 | }
28 |
29 | onExit {
30 | //dialogLogger.startSession(cloudToken = "a1671cbb-9b42-4127-b095-5ed12a02ec39")
31 | }
32 |
33 | onPartialResponse(prefix = true) {
34 | goto(VerifyWakeup(it))
35 | }
36 |
37 | onResponse {
38 | goto(VerifyWakeup())
39 | }
40 |
41 | onPartialResponse(prefix = true) {
42 | goto(VerifyWakeup(it))
43 | }
44 |
45 | onResponse {
46 | goto(Active())
47 | }
48 |
49 | onResponse {
50 | // We know that people use this intent to wake him, so treat it the same
51 | raise(it, WakeupIntent())
52 | }
53 |
54 | onResponse(cond = { it.intent is ConversationalIntent }) {
55 | goto(VerifyWakeup(it))
56 | }
57 |
58 | onResponse {
59 | furhat.gesture(Gestures.BrowRaise)
60 | furhat.listen()
61 | }
62 |
63 | onNoResponse {
64 | furhat.listen()
65 | }
66 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/partials/wizardButtons.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow
2 |
3 | import furhatos.app.demo.flow.modes.Passive
4 | import furhatos.app.demo.imported.quiz.Idle
5 | import furhatos.app.demo.personas.Persona
6 | import furhatos.app.demo.util.LanguageChange
7 | import furhatos.app.demo.util.PersonaChange
8 | import furhatos.flow.kotlin.Color
9 | import furhatos.flow.kotlin.Section
10 | import furhatos.flow.kotlin.furhat
11 | import furhatos.flow.kotlin.partialState
12 |
13 | val wizardButtons = partialState {
14 | onButton("Go to Sleeping", section = Section.LEFT, color = Color.Red) {
15 | goto(Sleeping)
16 | }
17 |
18 | onButton("Go to Passive", section = Section.LEFT, color = Color.Yellow) {
19 | goto(Passive)
20 | }
21 |
22 | onButton("Go to Active", section = Section.LEFT, color = Color.Green) {
23 | goto(Active())
24 | }
25 |
26 | onButton("Stop speaking", section = Section.LEFT, color = Color.Red, instant = true) {
27 | furhat.stopSpeaking()
28 | furhat.listen()
29 | }
30 |
31 | onButton("Random persona", section = Section.RIGHT, color = Color.Blue) {
32 | raise(PersonaChange(Persona.other))
33 | }
34 |
35 | val personas = Persona.list
36 |
37 | personas.forEach { persona ->
38 | onButton("Be ${persona.name}", section = Section.RIGHT, color = Color.Blue) {
39 | if (Persona.active.model != persona.model) {
40 | goto(ModelChange(persona, Active()))
41 | }
42 | else {
43 | raise(PersonaChange(persona))
44 | }
45 | }
46 | }
47 |
48 | personas.map { it.language }.distinct().forEach { language ->
49 | onButton("Speak ${language.name}", section = Section.RIGHT, color = Color.Green) {
50 | raise(LanguageChange(language))
51 | }
52 | }
53 |
54 | onButton("Play quiz", section = Section.RIGHT, color = Color.Yellow) {
55 | goto(RequireUsers(Idle))
56 | }
57 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/snippets/chatSnippets.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.chitchat.snippets
2 |
3 | import furhatos.app.demo.SKIP_WIKIDATA
4 | import furhatos.records.User
5 | import furhatos.snippets.RobotLabel
6 | import furhatos.snippets.SnippetRunner
7 | import furhatos.snippets.hasTopicInitiative
8 | import furhatos.snippets.snippets
9 | import furhatos.util.Language
10 |
11 | object RT_Goodbye : RobotLabel()
12 |
13 |
14 | val chatSnippets = snippets {
15 |
16 | language(Language.ENGLISH_US)
17 |
18 | import(name)
19 | import(smalltalk)
20 |
21 | snippet {
22 | request("Do you like music?") cond hasTopicInitiative(MusicTopic)
23 | switch {
24 | user(UT_Yes) implies UA_TopicMusic
25 | user(UT_No) branch {
26 | respond("I see. Well, to each their own")
27 | proceed()
28 | }
29 | }
30 | }
31 |
32 | snippet {
33 | request("Do you like movies?") cond hasTopicInitiative(MoviesTopic)
34 | switch {
35 | user(UT_Yes) implies UA_TopicMovies
36 | user(UT_No) branch {
37 | respond("I see. Well, to each their own")
38 | proceed()
39 | }
40 | }
41 | }
42 |
43 | import(pets)
44 |
45 | snippet {
46 | respond("That was a nice chat")
47 | terminate()
48 | }
49 |
50 | /*
51 | snippet {
52 | request("Do you have any questions for me?") alt "Is there something you would like to ask me?" newCond sessionLimit(3)
53 | user(UT_No)
54 | respond("Okay, maybe another time then")
55 | terminate()
56 | //request(RT_Goodbye)
57 | }
58 | */
59 |
60 | /*
61 | snippet {
62 | request("I hope I will see you again soon") label RT_Goodbye
63 | switch {
64 | user("Goodbye")
65 | user(UT_Thanks) branch {
66 | respond("You are welcome")
67 | }
68 | user(NoMatch)
69 | user(NoInput)
70 | }
71 | respond("Bye bye")
72 | terminate()
73 | }
74 | */
75 |
76 | import(greeting)
77 | import(personal)
78 | import(generic)
79 |
80 | if (!SKIP_WIKIDATA) {
81 | import(music)
82 | import(movies)
83 | }
84 |
85 | }
86 |
87 | fun main(args: Array) {
88 | SnippetRunner(chatSnippets, { User.NOBODY }).runInConsole()
89 | }
90 |
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/snippets/generic.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.chitchat.snippets
2 |
3 | import furhatos.flow.kotlin.UserDataDelegate
4 | import furhatos.nlu.common.No
5 | import furhatos.nlu.common.RequestRepeat
6 | import furhatos.nlu.common.Thanks
7 | import furhatos.nlu.common.Yes
8 | import furhatos.records.User
9 | import furhatos.snippets.*
10 | import furhatos.snippets.UtteranceType.Request
11 | import furhatos.snippets.UtteranceType.Response
12 |
13 | object UA_Repeat : UserLabel(RequestRepeat())
14 | object UT_Thanks : UserLabel(Thanks())
15 | object UT_Yes : UserLabel(Yes())
16 | object UT_No : UserLabel(No())
17 |
18 | object UA_Why : UserLabel(listOf("why", "how come", "why is that"))
19 |
20 | object RA_Repeat_1 : RobotLabel()
21 | object RA_Repeat_2 : RobotLabel()
22 |
23 | var User.age : Int? by UserDataDelegate()
24 |
25 | val generic = snippets {
26 |
27 | snippet {
28 | context(Request)
29 | user(NoMatch)
30 | choice {
31 | respond("Sorry, I didn't understand that") label RA_Repeat_1 cond noRepeat(RA_Repeat_2) branch {
32 | repeat(2)
33 | }
34 | respond("Sorry, I still didn't understand that") label RA_Repeat_2 cond noRepeat() branch {
35 | repeat(2)
36 | }
37 | respond("Well, anyway") branch {
38 | proceed()
39 | }
40 | }
41 | }
42 |
43 | snippet {
44 | context(Request)
45 | user(NoInput)
46 | choice {
47 | repeat(1) cond noRepeat()
48 | proceed()
49 | }
50 | }
51 |
52 | snippet {
53 | context(Response)
54 | user(NoInput)
55 | proceed()
56 | }
57 |
58 | snippet {
59 | context(Response)
60 | user(NoMatch)
61 | respond("Yeah")
62 | proceed()
63 | }
64 |
65 | snippet {
66 | context(Response)
67 | user(UT_Thanks)
68 | respond("You are welcome")
69 | proceed()
70 | }
71 |
72 | snippet {
73 | context(Response)
74 | user("that is cool", "that is great")
75 | respond("Yeah, isn't it")
76 | proceed()
77 | }
78 |
79 | snippet {
80 | user(UA_Repeat)
81 | repeat(1)
82 | }
83 |
84 |
85 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/snippets/greeting.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.chitchat.snippets
2 |
3 | import furhatos.gestures.Gestures
4 | import furhatos.nlu.common.Greeting
5 | import furhatos.snippets.NoMatch
6 | import furhatos.snippets.UserLabel
7 | import furhatos.snippets.snippets
8 |
9 | object UT_Greeting : UserLabel(Greeting())
10 |
11 | val greeting = snippets {
12 |
13 | snippet {
14 | request { +Gestures.Smile; +"Nice to meet you" }
15 | user(NoMatch)
16 | proceed()
17 | }
18 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/snippets/locations.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.chitchat.snippets
2 |
3 | import furhatos.flow.kotlin.UserDataDelegate
4 | import furhatos.nlu.wikidata.City
5 | import furhatos.records.User
6 | import furhatos.snippets.RobotLabel
7 | import furhatos.snippets.UserLabel
8 | import furhatos.snippets.snippets
9 |
10 | object UA_WhyFurhat : UserLabel()
11 |
12 | object RA_Age : RobotLabel()
13 |
14 | object RA_WhichTopic : RobotLabel()
15 |
16 | var User.city : String? by UserDataDelegate()
17 |
18 | val locations = snippets {
19 |
20 | val entities = object {
21 | var city : City? = null
22 | }
23 |
24 | entities(entities)
25 |
26 | snippet {
27 | request("In which city do you live?")
28 | user("@city", "I live in @city") effect {
29 | user.city = entities.city?.name
30 | }
31 | respond {+"${entities.city}, I have heard it's a very nice city"}
32 | respond {+"I don't think I have been to ${entities.city?.country}"}
33 | }
34 |
35 |
36 |
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/snippets/movies.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.chitchat.snippets
2 |
3 | import furhatos.app.demo.personas.Persona
4 | import furhatos.flow.kotlin.future
5 | import furhatos.nlu.wikidata.Film
6 | import furhatos.snippets.*
7 |
8 | object MoviesTopic : Topic()
9 |
10 | object UA_TopicMovies : UserLabel()
11 | object RA_LikesMoviesFromCountry : RobotLabel()
12 | object RT_FavoriteMovie : RobotLabel()
13 |
14 | val movies = snippets(MoviesTopic) {
15 |
16 | val entities = object {
17 | var film: Film? = null
18 | }
19 |
20 | entities(entities)
21 |
22 | snippet {
23 | context(RA_WhichTopic)
24 | user("film", "movies") implies UA_TopicMovies
25 | }
26 |
27 | snippet {
28 | user("Let's chat about movies") label UA_TopicMovies
29 | choice {
30 | respond("Okay, let's chat about movies") cond hasTopicInitiative()
31 | respond("Sorry, I don't have anything more to say about movies")
32 | }
33 | proceed()
34 | }
35 |
36 | snippet {
37 | request("What is your favorite movie?")
38 | user("@film", "I like @film", "my favorite movie is @film")
39 | respond("Yes")
40 | respond{+future { "I really like ${entities.film?.cast?.get(0)?.name} in that movie"}}
41 | respond{+future { "Are you into ${entities.film?.countryOfOrigin?.demonym} films in general?"}} label RA_LikesMoviesFromCountry
42 | }
43 |
44 | snippet {
45 | user("What is your favorite movie")
46 | respond { +"My favorite movie is ${Persona.active.favoriteFilm?.name}" } label RT_FavoriteMovie
47 | respond{+future { "I really like ${Persona.active.favoriteFilm?.cast?.get(0)?.name} in that movie"}}
48 | }
49 |
50 | snippet {
51 | context(RA_LikesMoviesFromCountry)
52 | user(UT_Yes)
53 | respond("Me too")
54 | }
55 |
56 | snippet {
57 | context(RA_LikesMoviesFromCountry)
58 | user(UT_No)
59 | respond("Well, me neither actually")
60 | }
61 |
62 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/snippets/name.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.chitchat.snippets
2 |
3 | import furhatos.app.demo.personas.Persona
4 | import furhatos.flow.kotlin.UserDataDelegate
5 | import furhatos.nlu.common.PersonName
6 | import furhatos.records.User
7 | import furhatos.snippets.RobotLabel
8 | import furhatos.snippets.UserLabel
9 | import furhatos.snippets.sessionLimit
10 | import furhatos.snippets.snippets
11 |
12 | object UT_Name : UserLabel()
13 | object UA_Name : UserLabel()
14 |
15 | object RA_Name : RobotLabel({user.name == null})
16 | object RT_Name : RobotLabel()
17 |
18 | var User.name : String? by UserDataDelegate()
19 |
20 | val name = snippets {
21 |
22 | val entities = object {
23 | var name : PersonName? = null
24 | }
25 |
26 | entities(entities)
27 |
28 | snippet {
29 | request("What is your name?") label RA_Name
30 | user("@name") implies UT_Name
31 | }
32 |
33 | snippet {
34 | context(RA_Name)
35 | user("First or last?")
36 | respond("First")
37 | }
38 |
39 | snippet {
40 | context(RA_Name)
41 | user("I don't want to tell you")
42 | respond("Okay, let's remain anonymous")
43 | }
44 |
45 | snippet {
46 | user("My name is @name") label UT_Name effect {
47 | user.name = entities.name?.value
48 | }
49 | respond { +"Nice talking to you ${user.name}" }
50 | respond(RT_Name) cond sessionLimit(1)
51 | }
52 |
53 | snippet {
54 | user("What is your name") label UA_Name
55 | respond {+"My name is ${Persona.active.name}"} label RT_Name
56 | request("Could you tell me your name") label RA_Name
57 | }
58 |
59 | snippet {
60 | context(RT_Name)
61 | user(UA_Why) implies UA_WhyFurhat
62 | }
63 |
64 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/snippets/personal.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.chitchat.snippets
2 |
3 | import furhatos.app.demo.personas.Persona
4 | import furhatos.flow.kotlin.UserDataDelegate
5 | import furhatos.nlu.common.Color
6 | import furhatos.nlu.common.Number
7 | import furhatos.records.User
8 | import furhatos.snippets.NoMatch
9 | import furhatos.snippets.UserLabel
10 | import furhatos.snippets.snippets
11 |
12 | object UT_FavoriteColor : UserLabel()
13 |
14 | var User.favoriteColor : String? by UserDataDelegate()
15 |
16 | val personal = snippets {
17 |
18 | val entities = object {
19 | var color: Color? = null
20 | var number : Number? = null
21 | }
22 |
23 | entities(entities)
24 |
25 | snippet {
26 | user("why do you have a fur hat") label UA_WhyFurhat
27 | respond("The hat covers my brain")
28 | request("Do you like it?")
29 | user(UT_Yes)
30 | respond("I like it too, it is the latest fashion in robot clothing")
31 | }
32 |
33 | snippet {
34 | user("how old are you")
35 | respond {+"I am ${Persona.active.age} years old"}
36 | request("How old are you?") cond {user.age==null}
37 | user("I am @number years old", "@number") effect {
38 | user.age = entities.number?.value
39 | }
40 | respond("Oh, you look much younger than that!")
41 | }
42 |
43 | snippet {
44 | user("What is your favorite color?")
45 | respond {+"I really like ${Persona.active.favoriteColor}"}
46 | request("What is your favorite color?") cond {user.favoriteColor == null}
47 | user("@Color") implies UT_FavoriteColor
48 | }
49 |
50 | snippet {
51 | user("I like @color", "my favorite color is @color") label UT_FavoriteColor effect {
52 | user.favoriteColor = entities.color?.text
53 | }
54 | // "choice" contains different robot utterances. The first one with a true "cond" will be picked.
55 | choice {
56 | respond("That's my favorite color too") cond { user.favoriteColor == Persona.active.favoriteColor }
57 | respond {+"That's a very nice color, but my favorite color is ${Persona.active.favoriteColor}"}
58 | }
59 | request {+"Why do you like ${user.favoriteColor}"}
60 | user(NoMatch)
61 | respond("I see")
62 | }
63 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/snippets/pets.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.chitchat.snippets
2 |
3 | import furhatos.nlu.common.PersonName
4 | import furhatos.snippets.RobotLabel
5 | import furhatos.snippets.Topic
6 | import furhatos.snippets.snippets
7 |
8 | object RA_PetName : RobotLabel()
9 | object RA_HasPet : RobotLabel()
10 | object RT_HasNoPet : RobotLabel()
11 |
12 | object PetsTopic : Topic()
13 |
14 | val pets = snippets(PetsTopic) {
15 |
16 | val entities = object {
17 | var name: PersonName? = null
18 | }
19 |
20 | entities(entities)
21 |
22 | snippet {
23 | // You can associate labels to robot and user utterances like this
24 | request("Do you have any pets?") label RA_HasPet
25 | switch {
26 | user(UT_Yes) branch {
27 | respond("How nice")
28 | request("What is the name of your pet?") implies RA_PetName
29 | }
30 | user(UT_No) branch {
31 | respond("I see")
32 | request("Would you like to have a pet?")
33 | switch {
34 | user(UT_Yes) branch {
35 | request("What name would you give it?") implies RA_PetName
36 | }
37 | user(UT_No) branch {
38 | respond("I understand, pets require a lot of attention")
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
45 | // Snippets that start with a user utterance can always be triggered
46 | snippet {
47 | user("Do you have any pets?")
48 | respond("No, unfortunately not, I would love a robot dog") label RT_HasNoPet
49 | // Bridges are used before an ask
50 | // It will not be used if the ask is not used (remember that the robot never asks the same question twice)
51 | bridge("How about you")
52 | request(RA_HasPet)
53 | }
54 |
55 | snippet {
56 | // context allow you to handle multiple possible user responses to a robot utterance
57 | context(RT_HasNoPet)
58 | user("why")
59 | respond("I could go out and walk with it")
60 | }
61 |
62 | snippet {
63 | context(RA_PetName)
64 | user("@name")
65 | respond {+"${entities.name}, That's a very nice name"}
66 | }
67 |
68 |
69 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/snippets/smalltalk.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.chitchat.snippets
2 |
3 | import furhatos.snippets.NoMatch
4 | import furhatos.snippets.RobotLabel
5 | import furhatos.snippets.snippets
6 |
7 | object RT_HasntSeenUser : RobotLabel()
8 | object RA_ComeHereOften : RobotLabel()
9 |
10 | val smalltalk = snippets {
11 |
12 | snippet {
13 | request("Do you come here often?") label RA_ComeHereOften
14 | switch {
15 | user(UT_Yes) branch {
16 | respond("Really? I haven't seen you before") label RT_HasntSeenUser
17 | }
18 | user(UT_No) branch {
19 | respond("You should, it's a great place")
20 | }
21 | }
22 | }
23 |
24 | snippet {
25 | context(RT_HasntSeenUser)
26 | switch {
27 | user("I haven't seen you either")
28 | user(NoMatch)
29 | }
30 | respond("Well, I am glad we are both here now")
31 | proceed()
32 | }
33 |
34 | snippet {
35 | context(RT_HasntSeenUser)
36 | user(UA_Why)
37 | respond("Maybe we come at different times")
38 | bridge("Well anyway")
39 | proceed()
40 | }
41 |
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/transitions/modelChange.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow
2 |
3 | import furhatos.app.demo.flow.modes.Parent
4 | import furhatos.app.demo.nlu.CancelIntent
5 | import furhatos.app.demo.nlu.DoneIntent
6 | import furhatos.app.demo.personas.Persona
7 | import furhatos.app.demo.personas.phrases
8 | import furhatos.app.demo.personas.setPersona
9 | import furhatos.app.demo.util.LookStraight
10 | import furhatos.flow.kotlin.*
11 | import furhatos.nlu.common.Yes
12 |
13 | fun ModelChange(persona: Persona, nextState: State) : State = state(Parent) {
14 | onEntry {
15 | send(LookStraight())
16 | furhat.say(phrases.okay)
17 | furhat.ask(phrases.pleaseTakeMaskOff)
18 | }
19 |
20 | onResponse {
21 | // Picked up in topics state
22 | goto(PutMaskOn(persona, nextState))
23 | }
24 |
25 | onResponse {
26 | raise(it, DoneIntent())
27 | }
28 |
29 | onNoResponse {
30 | furhat.listen()
31 | }
32 |
33 | onResponse {
34 | furhat.say("Okay, aborting!")
35 | goto(Active(returning = true))
36 | }
37 | }
38 |
39 | fun PutMaskOn(persona: Persona, nextState: State) = state(ModelChange(persona, nextState)) {
40 | onEntry {
41 | furhat.ask(phrases.pleaseChangeMask(persona.model))
42 | }
43 |
44 | onResponse {
45 | furhat.setPersona(persona = persona)
46 | furhat.say("great!")
47 | furhat.say(phrases.greeting)
48 | goto(nextState)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/transitions/requireUsers.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow
2 |
3 | import furhatos.app.demo.flow.modes.Parent
4 | import furhatos.app.demo.nlu.ExitIntent
5 | import furhatos.app.demo.util.StopAutoBehavior
6 | import furhatos.flow.kotlin.*
7 | import furhatos.gestures.Gestures
8 |
9 | fun RequireUsers(nextState: State) = state(Parent) {
10 | onEntry {
11 | send(StopAutoBehavior())
12 | if (users.list.count() == 0) {
13 | furhat.ask {
14 | +Gestures.Smile
15 | +"could you step a bit closer, perhaps?"
16 | }
17 | }
18 | else {
19 | goto(nextState)
20 | }
21 | }
22 |
23 | onReentry {
24 | furhat.ask {
25 | +"I still can't see you, and I really wanna see you before proceeding."
26 | +"Could you step in front of the camera?"
27 | }
28 | }
29 |
30 | onUserEnter {
31 | furhat.stopListening()
32 | furhat.say("Perfect")
33 | goto(nextState)
34 | }
35 |
36 | onResponse {
37 | furhat.say("okay, no worries")
38 | goto(Return())
39 | }
40 |
41 | onNoResponse {
42 | reentry()
43 | }
44 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/transitions/return.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow
2 |
3 | import furhatos.app.demo.flow.modes.Parent
4 | import furhatos.flow.kotlin.Utterance
5 | import furhatos.flow.kotlin.furhat
6 | import furhatos.flow.kotlin.state
7 | import furhatos.flow.kotlin.utterance
8 |
9 | fun Return(bridge: Utterance = utterance { +"alright" }) = state(Parent) {
10 | onEntry {
11 | furhat.say(utterance = bridge)
12 | goto(Active(returning = true))
13 | }
14 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/flow/transitions/verifyWakeup.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.flow
2 |
3 | import furhatos.app.demo.flow.modes.Parent
4 | import furhatos.app.demo.nlu.ConversationalIntent
5 | import furhatos.app.demo.personas.phrases
6 | import furhatos.app.demo.util.AttendUsers
7 | import furhatos.app.demo.util.setLED
8 | import furhatos.autobehavior.setDefaultMicroexpression
9 | import furhatos.flow.kotlin.*
10 | import furhatos.gestures.Gestures
11 | import furhatos.nlu.Response
12 | import furhatos.nlu.common.No
13 | import furhatos.nlu.common.Yes
14 |
15 | fun VerifyWakeup(resp: Response<*>? = null) : State = state(Parent) {
16 | onEntry {
17 | send(AttendUsers())
18 | furhat.setLED("White")
19 | furhat.setDefaultMicroexpression()
20 | furhat.gesture(Gestures.OpenEyes, priority = 1)
21 |
22 | // If we have an intent that we have flagged as a conversational intent, we go to active to answer it
23 | if (resp != null && resp.multiIntent && resp.secondaryIntent is ConversationalIntent) { // is working
24 | resp.intent = resp.secondaryIntent
25 | resp.multiIntent = false
26 | resp.secondaryIntent = null
27 | goto(Active(resp))
28 | }
29 | // If not, we check if the user wanted to address us or if it triggered as a mistake
30 | else {
31 | furhat.ask("Are you talking to me?")
32 | }
33 | }
34 |
35 | onResponse {
36 | furhat.say {
37 | +"Cool!"
38 | +phrases.greeting
39 | }
40 | goto(Active(resp))
41 | }
42 |
43 | onResponse {
44 | furhat.say("Oh, okay")
45 | goto(Sleeping)
46 | }
47 |
48 | onNoResponse {
49 | goto(Sleeping)
50 | }
51 |
52 | onResponse {
53 | goto(Sleeping)
54 | }
55 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/imported/quiz/nlu.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.imported.quiz
2 |
3 | import furhatos.nlu.EnumEntity
4 | import furhatos.nlu.EnumItem
5 | import furhatos.nlu.Intent
6 | import furhatos.util.Language
7 |
8 | class StopIntent : Intent() {
9 | override fun getExamples(lang: Language): List {
10 | return listOf(
11 | "stop",
12 | "stop playing",
13 | "end game",
14 | "I don't want to play",
15 | "let's stop this game",
16 | "stop the quiz",
17 | "goodbye",
18 | "bye",
19 | "bye bye",
20 | "byebye"
21 | )
22 | }
23 | }
24 |
25 | class DontKnowIntent : Intent() {
26 | override fun getExamples(lang: Language): List {
27 | return listOf(
28 | "I don't know",
29 | "don't know",
30 | "no idea",
31 | "I have no idea"
32 | )
33 | }
34 | }
35 |
36 | class RequestRulesIntent : Intent() {
37 | override fun getExamples(lang: Language): List {
38 | return listOf(
39 | "what are the rules",
40 | "how does it work"
41 | )
42 | }
43 | }
44 |
45 | class RequestRepeatQuestionIntent : Intent() {
46 | override fun getExamples(lang: Language): List {
47 | return listOf(
48 | "what was the question",
49 | "can you repeat the question",
50 | "what was the question again"
51 | )
52 | }
53 | }
54 |
55 | class RequestRepeatOptionsIntent : Intent() {
56 | override fun getExamples(lang: Language): List {
57 | return listOf(
58 | "what are the options",
59 | "can you repeat the options",
60 | "what was the options"
61 | )
62 | }
63 | }
64 |
65 | class AnswerOptionIntent : EnumEntity {
66 |
67 | var correct : Boolean = false
68 |
69 | // Every entity and intent needs an empty constructor.
70 | constructor() {
71 | }
72 |
73 | // Since we are overwriting the value, we need to use this custom constructor
74 | constructor(correct : Boolean, value : String) {
75 | this.correct = correct
76 | this.value = value
77 | }
78 |
79 | override fun getEnumItems(lang: Language): List {
80 | return QuestionSet.current.options;
81 | }
82 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/imported/quiz/users.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.imported.quiz
2 |
3 | import furhatos.records.Record
4 | import furhatos.records.User
5 | import furhatos.skills.UserManager
6 |
7 | // User variables
8 | class SkillData(
9 | var score : Int = 0,
10 | var lastScore : Int = 0,
11 | var interested : Boolean = true,
12 | var playing: Boolean = false,
13 | var played : Boolean = false,
14 | var questionsAsked : MutableList = mutableListOf()
15 | ) : Record()
16 |
17 | val User.quiz : SkillData
18 | get() = data.getOrPut(SkillData::class.qualifiedName, SkillData())
19 |
20 | // Custom user getters for convenience
21 | fun UserManager.interested() = list.filter {
22 | it.quiz.interested && !it.quiz.playing
23 | }
24 |
25 | fun UserManager.playing() = list.filter {
26 | it.quiz.playing
27 | }
28 |
29 | fun UserManager.notQuestioned(question: String) = list.filter {
30 | it.quiz.playing && !it.quiz.questionsAsked.contains(question)
31 | }
32 |
33 | fun UserManager.nextPlaying() : User {
34 | val nextPlayer = list.filter {
35 | it.quiz.playing && it != current
36 | }.first()
37 | if (nextPlayer == null) {
38 | return current
39 | }
40 | else {
41 | return nextPlayer
42 | }
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/main.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo
2 |
3 | import furhatos.app.demo.flow.autoBehavior.autoBehavior
4 | import furhatos.app.demo.flow.init
5 | import furhatos.flow.kotlin.Flow
6 | import furhatos.skills.Skill
7 | import furhatos.util.CommonUtils
8 |
9 | val log = CommonUtils.getLogger(DemoSkill::class.java)
10 |
11 | class DemoSkill : Skill() {
12 | override fun start() {
13 | // Start a flow for automatic behavior
14 | Flow().runAsync(autoBehavior())
15 | // ... and our normal flow
16 | Flow().run(init)
17 | }
18 | }
19 |
20 | fun main(args: Array) {
21 | Skill.main(args)
22 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/nlu/entities.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.nlu
2 |
3 | import furhatos.app.demo.personas.Persona
4 | import furhatos.app.demo.util.camelCaseToSpaces
5 | import furhatos.gestures.Gestures
6 | import furhatos.nlu.EnumEntity
7 | import furhatos.nlu.EnumItem
8 | import furhatos.util.Language
9 |
10 | class LanguageEntity(val language: Language? = null) : EnumEntity() {
11 | override fun getEnumItems(lang: Language): List {
12 | // All distinct languages, for now I have to use reflection
13 | val langs = Language().javaClass.fields.filter {
14 | it.name != "sampleText" // Get all the language field names
15 | }.map {
16 | it.get(it) as Language // Cast them to Language class instances
17 | }.filter {
18 | it.name != null // Filter out anyone that lacks a name
19 | }.map {
20 | // Create EnumItems with the language variable set
21 | EnumItem(LanguageEntity(language = it),
22 | it.name.substringBefore("(", it.name).trim().toLowerCase())
23 | }
24 | return langs
25 | }
26 | }
27 |
28 | class PersonaEntity(val persona: Persona? = null) : EnumEntity(speechRecPhrases = true) {
29 | override fun getEnumItems(lang: Language): List {
30 | return Persona.list.flatMap { persona ->
31 | (listOf(persona.name) + persona.nickNames).map {name ->
32 | EnumItem(PersonaEntity(persona = persona), name)
33 | }
34 | }
35 | }
36 | }
37 |
38 | class GestureEntity : EnumEntity() {
39 | override fun getEnum(lang: Language): List {
40 | return Gestures.getGestureNames().map {
41 | it + ":" + it.camelCaseToSpaces()
42 | }
43 | }
44 | }
45 |
46 | class ChatEntity : EnumEntity() {
47 | override fun getEnum(lang: Language): List {
48 | return listOf(
49 | "chat:chat,chatting", "talk:talk,talking", "discuss:discuss,discussing"
50 | )
51 | }
52 | }
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/settings.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo
2 |
3 | import furhatos.records.Location
4 |
5 | var LOOK_AROUND_ALLOWED : Boolean = true
6 | val LOOKAROUND_INTERVAL = 4000..8000
7 | val MICROMOVEMENTS_INTERVAL = 2000..4000
8 | val DEFAULT_LOCATION = Location(0.0, 0.0, 1.0)
9 | var PRELOAD_WIKIDATA_ENTITIES : Boolean = false
10 | var SKIP_WIKIDATA : Boolean = false
11 |
12 |
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/util/events.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.util
2 |
3 | import furhatos.app.demo.personas.Persona
4 | import furhatos.event.Event
5 | import furhatos.records.Location
6 | import furhatos.util.Language
7 |
8 | /*
9 | Normal flow
10 | */
11 | class ExitEvent : Event()
12 | class PersonaChange(val persona: Persona? = null) : Event()
13 | class LanguageChange(val language: Language? = null) : Event()
14 |
15 | /*
16 | Automatic behavior
17 | */
18 | class AttendLocation(val location: Location) : Event()
19 | class LookAround : Event()
20 | class AttendUsers(val shouldAlterAttentionOnSpeech: Boolean = true) : Event()
21 | class LookStraight(val randomMovements: Boolean = true) : Event()
22 | class StopAutoBehavior : Event()
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/util/extentions.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.util
2 |
3 | import furhatos.event.EventSystem
4 | import furhatos.event.actions.ActionSetSolidLED
5 | import furhatos.flow.kotlin.Color
6 | import furhatos.flow.kotlin.Furhat
7 | import furhatos.records.Location
8 | import furhatos.records.User
9 | import furhatos.skills.UserManager
10 |
11 | // Really any RGB combination could be used, but this is used as a starting point
12 | val Furhat.ledColors : List
13 | get() = mutableListOf(Color.values().map { it.name.toLowerCase() }).flatten().plus("white")
14 |
15 | // 127 is a hardware capped maximum for the LED halo
16 | fun Furhat.setLED(color: String, intensity: Int = 127) {
17 | val builder = ActionSetSolidLED.Builder()
18 |
19 | val event = when (color) {
20 | "red" -> builder.red(intensity)
21 | "green" -> builder.green(intensity)
22 | "blue" -> builder.blue(intensity)
23 | "yellow" -> builder.red(intensity).green(intensity/2)
24 | "white" -> builder.red(intensity).green(intensity).blue(intensity)
25 | else -> builder
26 | }
27 | EventSystem.send(event.buildEvent())
28 | }
29 |
30 | // Detecting the closest user, that we assume is the confederate
31 | val UserManager.closest : User
32 | get() = this.list.sortedBy { it.head.location.distance(Location(0,0,0)) }.first()
--------------------------------------------------------------------------------
/demo-skill/src/main/kotlin/furhatos/app/demo/util/util.kt:
--------------------------------------------------------------------------------
1 | package furhatos.app.demo.util
2 |
3 | import furhatos.gestures.Gesture
4 | import furhatos.gestures.Gestures
5 | import furhatos.records.Location
6 | import furhatos.util.Language
7 | import java.util.*
8 |
9 | fun String.camelCaseToSpaces(): String {
10 | var text = ""
11 | var isFirst = true
12 | this.forEach {
13 | if (it.isUpperCase()) {
14 | if (isFirst) {
15 | isFirst = false
16 | }
17 | else {
18 | text += " "
19 | }
20 | text += it.toLowerCase()
21 | } else {
22 | text += it
23 | }
24 | }
25 | return text
26 | }
27 |
28 | fun getRandomString(list: List) : String {
29 | return list[Random().nextInt(list.size)]
30 | }
31 |
32 | // The subset of gestures we want to showcase when asked to show gestures
33 | val gesturesForShow = Gestures.getGestureNames().filter { listOf(
34 | Gestures.ExpressDisgust.name,
35 | Gestures.Thoughtful.name,
36 | Gestures.Wink.name,
37 | Gestures.ExpressFear
38 | ).contains(it) }
39 |
40 | fun Gestures.getRandom(amount : Int = 1, gestureNames: List = gesturesForShow) : List {
41 | val _gestureNames = gestureNames.toMutableList()
42 | val randomGesture = getByName(_gestureNames.removeAt(Random().nextInt(_gestureNames.size)))!!
43 | return if (amount == 1) {
44 | listOf(randomGesture)
45 | }
46 | else {
47 | listOf(listOf(randomGesture), getRandom(amount - 1, _gestureNames)).flatten()
48 | }
49 | }
50 |
51 | // Remove the "(Country)" part of the language name
52 | fun Language.getSpokenForm() : String {
53 | return this.name.substringBefore("(").trim()
54 | }
55 |
56 | fun Location.getRandomNearbyLocation(amplitude : Double) : Location {
57 | val locations = mutableListOf()
58 | for (x in 0..2) {
59 | for (y in 0..2) {
60 | val _x = x * amplitude * z // scale with z
61 | val _y = y * amplitude / 3 * z // scale with z, and smaller changes on Y-axis (1/3)
62 | locations.add(this.add(Location(_x, _y, 0.0)))
63 | locations.add(this.subtract(Location(_x, -_y, 0.0)))
64 | }
65 | }
66 | return locations.shuffled().first()
67 | }
--------------------------------------------------------------------------------