├── .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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 | } --------------------------------------------------------------------------------