add comms
This commit is contained in:
commit
1395669d67
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
node_modules/
|
||||||
|
/out
|
||||||
|
/build
|
||||||
|
build/
|
||||||
|
/.gradle
|
||||||
|
/logs
|
||||||
|
/.idea
|
||||||
|
|
||||||
|
save_flow.xml
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Python virtual environment
|
||||||
|
**/.venv/
|
||||||
|
**/venv/
|
||||||
|
|
||||||
|
# Python cache
|
||||||
|
**/__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
### Projekt obsahuje Furhat skill určený na podporu vybraných kognitívnych funkcií používateľa.
|
||||||
|
|
||||||
|
## Obsah projektu
|
||||||
|
|
||||||
|
- `src/main/kotlin` – hlavná implementácia Furhat skillu
|
||||||
|
- `src/main/resources` – zdroje používané skillom
|
||||||
|
- `src/main/realtime` – proxy modul pre komunikáciu s OpenAI Realtime API
|
||||||
|
- `assets` – doplnkové súbory aplikácie, ak sa používajú
|
||||||
|
- `skill.properties` – konfiguračný súbor Furhat skillu
|
||||||
|
|
||||||
|
## Spustenie skillu
|
||||||
|
|
||||||
|
1. Otvoriť projekt v IntelliJ IDEA.
|
||||||
|
2. Skontrolovať konfiguráciu Furhat SDK.
|
||||||
|
3. Spustiť skill cez Gradle alebo cez Run konfiguráciu.
|
||||||
|
|
||||||
|
## Spustenie proxy modulu
|
||||||
|
|
||||||
|
1. Vytvoriť `.env` podľa súboru `.env.example`.
|
||||||
|
2. Nainštalovať potrebné Python balíky.
|
||||||
|
3. Spustiť FastAPI server.
|
||||||
|
|
||||||
|
## Poznámka
|
||||||
|
|
||||||
|
Súbory s logmi a súkromné konfiguračné údaje nie sú súčasťou repozitára.
|
||||||
7
assets/webTemplates/BASIC/css/bootstrap.min.css
vendored
Normal file
7
assets/webTemplates/BASIC/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
18
assets/webTemplates/BASIC/css/style.css
Normal file
18
assets/webTemplates/BASIC/css/style.css
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
*{
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body{
|
||||||
|
background: linear-gradient(-60deg, #ff5858 0%, #f09819 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.center{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p{
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
342
assets/webTemplates/BASIC/dist/bundle.js
vendored
Normal file
342
assets/webTemplates/BASIC/dist/bundle.js
vendored
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
/******/ (function(modules) { // webpackBootstrap
|
||||||
|
/******/ // The module cache
|
||||||
|
/******/ var installedModules = {};
|
||||||
|
/******/
|
||||||
|
/******/ // The require function
|
||||||
|
/******/ function __webpack_require__(moduleId) {
|
||||||
|
/******/
|
||||||
|
/******/ // Check if module is in cache
|
||||||
|
/******/ if(installedModules[moduleId]) {
|
||||||
|
/******/ return installedModules[moduleId].exports;
|
||||||
|
/******/ }
|
||||||
|
/******/ // Create a new module (and put it into the cache)
|
||||||
|
/******/ var module = installedModules[moduleId] = {
|
||||||
|
/******/ i: moduleId,
|
||||||
|
/******/ l: false,
|
||||||
|
/******/ exports: {}
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Execute the module function
|
||||||
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||||
|
/******/
|
||||||
|
/******/ // Flag the module as loaded
|
||||||
|
/******/ module.l = true;
|
||||||
|
/******/
|
||||||
|
/******/ // Return the exports of the module
|
||||||
|
/******/ return module.exports;
|
||||||
|
/******/ }
|
||||||
|
/******/
|
||||||
|
/******/
|
||||||
|
/******/ // expose the modules object (__webpack_modules__)
|
||||||
|
/******/ __webpack_require__.m = modules;
|
||||||
|
/******/
|
||||||
|
/******/ // expose the module cache
|
||||||
|
/******/ __webpack_require__.c = installedModules;
|
||||||
|
/******/
|
||||||
|
/******/ // identity function for calling harmony imports with the correct context
|
||||||
|
/******/ __webpack_require__.i = function(value) { return value; };
|
||||||
|
/******/
|
||||||
|
/******/ // define getter function for harmony exports
|
||||||
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||||
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||||
|
/******/ Object.defineProperty(exports, name, {
|
||||||
|
/******/ configurable: false,
|
||||||
|
/******/ enumerable: true,
|
||||||
|
/******/ get: getter
|
||||||
|
/******/ });
|
||||||
|
/******/ }
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||||
|
/******/ __webpack_require__.n = function(module) {
|
||||||
|
/******/ var getter = module && module.__esModule ?
|
||||||
|
/******/ function getDefault() { return module['default']; } :
|
||||||
|
/******/ function getModuleExports() { return module; };
|
||||||
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||||
|
/******/ return getter;
|
||||||
|
/******/ };
|
||||||
|
/******/
|
||||||
|
/******/ // Object.prototype.hasOwnProperty.call
|
||||||
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||||
|
/******/
|
||||||
|
/******/ // __webpack_public_path__
|
||||||
|
/******/ __webpack_require__.p = "/dist/";
|
||||||
|
/******/
|
||||||
|
/******/ // Load entry module and return exports
|
||||||
|
/******/ return __webpack_require__(__webpack_require__.s = 2);
|
||||||
|
/******/ })
|
||||||
|
/************************************************************************/
|
||||||
|
/******/ ([
|
||||||
|
/* 0 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
module.exports = __webpack_require__(3)
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 1 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
module.exports = __webpack_require__(4)
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 2 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
var _furhatCore = __webpack_require__(0);
|
||||||
|
|
||||||
|
var _furhatCore2 = _interopRequireDefault(_furhatCore);
|
||||||
|
|
||||||
|
var _furhatGui = __webpack_require__(1);
|
||||||
|
|
||||||
|
var _furhatGui2 = _interopRequireDefault(_furhatGui);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
(0, _furhatGui2.default)(function (furhat) {
|
||||||
|
furhat.subscribe('furhatos.event.responses.ResponseSkillGUIName', function (data) {
|
||||||
|
if (data.port == window.location.port) {
|
||||||
|
setPageTitle(data.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
furhat.subscribe('furhatos.event.actions.ActionSkillGUIClear', function (data) {
|
||||||
|
if (data.port == window.location.port) {
|
||||||
|
clearScreen();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
furhat.subscribe('furhatos.event.actions.ActionSkillGUIWrite', function (data) {
|
||||||
|
if (data.port == window.location.port) {
|
||||||
|
clearScreen();
|
||||||
|
appendText(data.text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
furhat.subscribe('furhatos.event.actions.ActionSkillGUIAppend', function (data) {
|
||||||
|
if (data.port == window.location.port) {
|
||||||
|
appendText(data.text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
furhat.send({
|
||||||
|
event_name: 'furhatos.event.requests.RequestSkillGUIName',
|
||||||
|
port: window.location.port
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setPageTitle(title) {
|
||||||
|
document.getElementsByTagName("title")[0].innerText = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendText(text) {
|
||||||
|
var p = document.createElement('p');
|
||||||
|
|
||||||
|
p.innerText = text;
|
||||||
|
document.getElementById('root').appendChild(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearScreen() {
|
||||||
|
var root = document.getElementById('root');
|
||||||
|
|
||||||
|
while (root.firstChild) {
|
||||||
|
root.removeChild(root.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 3 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
// Constants
|
||||||
|
const OPEN = 'open';
|
||||||
|
const CLOSE = 'closed';
|
||||||
|
const FAIL = 'failed';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Furhat main class. Maintains the websocket connection to furhatOS and
|
||||||
|
* has methods to send events, subscribe to events and helper methods such as say,
|
||||||
|
* gesture, etc.
|
||||||
|
*/
|
||||||
|
class Furhat {
|
||||||
|
constructor() {
|
||||||
|
this.eventFunctions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the furhat socket connection and executes the callback method.
|
||||||
|
* @param domain IP Address for furhatOS - localhost if SDK.
|
||||||
|
* @param port port for RealTimeAPI module of furhatOS.
|
||||||
|
* @param route route for RealTimeAPI module of furhatOS.
|
||||||
|
* @param callback callback method to be executed on successful opening of websocket.
|
||||||
|
*/
|
||||||
|
init(domain, port, route, callback) {
|
||||||
|
if (this.socket !== undefined) {
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
console.log(`initializing ws://${domain}:${port}/${route}`); // eslint-disable-line no-console
|
||||||
|
this.socket = new window.WebSocket(`ws://${domain}:${port}/${route}`); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
this.socket.onopen = () => {
|
||||||
|
this.status = OPEN;
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(OPEN, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.socket.onmessage = event => {
|
||||||
|
if (this.eventFunctions[JSON.parse(event.data).event_name] !== undefined) {
|
||||||
|
this.eventFunctions[JSON.parse(event.data).event_name](JSON.parse(event.data));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.socket.onclose = () => {
|
||||||
|
this.status = CLOSE;
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(CLOSE, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.socket.onerror = () => {
|
||||||
|
this.status = FAIL;
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(FAIL, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an event to furhatOS
|
||||||
|
* @param event Object containing the event. Mandtory to have event_name parameter in the object
|
||||||
|
*/
|
||||||
|
send(event) {
|
||||||
|
if (this.socket.readyState === 2 || this.socket.readyState === 3) {
|
||||||
|
// SHIT
|
||||||
|
} else if (this.socket.readyState === 1) {
|
||||||
|
this.socket.send(JSON.stringify(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to the given event and triggers the supplied callback on event
|
||||||
|
* @param eventName Name of the event to subscribe
|
||||||
|
* @param callback Function which needs to be triggered when the given event is recieved
|
||||||
|
* @param dontSend [Optional] [false by default] Boolean which determines wether to send
|
||||||
|
* the subscribe event or not. use it to set callbacks for event that are already subscribed to,
|
||||||
|
* for instance with group subscriptions
|
||||||
|
*/
|
||||||
|
subscribe(eventName, callback, dontSend = false) {
|
||||||
|
const event = { event_name: 'furhatos.event.actions.ActionRealTimeAPISubscribe', name: eventName };
|
||||||
|
this.eventFunctions[eventName] = callback;
|
||||||
|
if (!dontSend) {
|
||||||
|
this.send(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to the given event group
|
||||||
|
* @param groupNumber Number(Assigned ENUM) of the group that needs to be subscribed to
|
||||||
|
*/
|
||||||
|
subscribeGroup(groupNumber) {
|
||||||
|
const event = { event_name: 'furhatos.event.actions.ActionRealTimeAPISubscribe', group: groupNumber };
|
||||||
|
this.send(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Says a given text
|
||||||
|
* @param text Text which needs to be said by Furhat
|
||||||
|
*/
|
||||||
|
say(text) {
|
||||||
|
const event = { event_name: 'furhatos.event.actions.ActionSpeech', text };
|
||||||
|
this.send(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stimulates the speech of a user in the interaction space
|
||||||
|
* @param text Text which needs to be said by the user
|
||||||
|
*/
|
||||||
|
userSpeech(text) {
|
||||||
|
const event = { event_name: 'furhatos.event.senses.SenseTypingEnd', messageText: text };
|
||||||
|
this.send(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stimulates SenseSpeechStart event. Can be used to stimulate user speech via typing
|
||||||
|
*/
|
||||||
|
userSpeechStart() {
|
||||||
|
const event = { event_name: 'furhatos.event.senses.SenseTypingStart' };
|
||||||
|
this.send(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the given gesture
|
||||||
|
* @param name Name of the gesture that needs to be performed
|
||||||
|
*/
|
||||||
|
gesture(name) {
|
||||||
|
const event = { event_name: 'furhatos.event.actions.ActionGesture', name };
|
||||||
|
this.send(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.default = Furhat;
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
/* 4 */
|
||||||
|
/***/ (function(module, exports, __webpack_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _furhatCore = __webpack_require__(0);
|
||||||
|
|
||||||
|
var _furhatCore2 = _interopRequireDefault(_furhatCore);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
let portNumber;
|
||||||
|
let callbackFun;
|
||||||
|
|
||||||
|
const InitCallback = (status, hat) => {
|
||||||
|
if (status === 'open') {
|
||||||
|
hat.send({
|
||||||
|
event_name: 'furhatos.event.senses.SenseSkillGUIConnected',
|
||||||
|
port: portNumber
|
||||||
|
});
|
||||||
|
callbackFun(hat);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FurhatGUI Function which sets up a connection to the furhat skill and gives
|
||||||
|
* the furhat object to send and recieve events to the skill.
|
||||||
|
* @param callback callback that needs to be triggered when a sucessful connection is established
|
||||||
|
*/
|
||||||
|
const FurhatGUI = callback => {
|
||||||
|
if (callback !== undefined && typeof callback === 'function') {
|
||||||
|
window.fetch('/port', { method: 'GET' }).then(r => {
|
||||||
|
// eslint-disable-line no-undef
|
||||||
|
r.json().then(o => {
|
||||||
|
const furhat = new _furhatCore2.default();
|
||||||
|
portNumber = o.port;
|
||||||
|
callbackFun = callback;
|
||||||
|
furhat.init(o.address, o.port, 'api', InitCallback); // eslint-disable-line no-undef
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.default = FurhatGUI;
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
/******/ ]);
|
||||||
21
assets/webTemplates/BASIC/index.html
Normal file
21
assets/webTemplates/BASIC/index.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/style.css">
|
||||||
|
<title>Furhat Skill</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div id="root" class="center">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="dist/bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
70
build.gradle
Normal file
70
build.gradle
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.jvm' version '1.8.21'
|
||||||
|
id 'com.github.johnrengelman.shadow' version '6.1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
|
||||||
|
//Defines what version of Java to use.
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
|
||||||
|
//Defines how Kotlin should compile.
|
||||||
|
compileKotlin {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
//Defines what jvm bytecode to use, 1.8 rather than 1.6
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
apiVersion = "1.8"
|
||||||
|
languageVersion = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Defines how Kotlin should compile when testingTry to keep it the same as compileKotlin.
|
||||||
|
compileTestKotlin {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
//Defines what jvm bytecode to use, 1.8 rather than 1.6
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
apiVersion = "1.8"
|
||||||
|
languageVersion = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
maven { url "https://s3-eu-west-1.amazonaws.com/furhat-maven/releases"}
|
||||||
|
maven { url 'https://repo.gradle.org/gradle/libs-releases' }
|
||||||
|
maven { url { "https://repo1.maven.org/maven2/" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'com.furhatrobotics.furhatos:furhat-commons:2.9.0'
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
def lowerCasedName = baseName.toLowerCase()
|
||||||
|
def normalizedName = lowerCasedName.substring(0,1).toUpperCase() + lowerCasedName.substring(1)
|
||||||
|
manifest.attributes(
|
||||||
|
'Class-Path': configurations.compileClasspath.collect { it.getName() }.join(' '),
|
||||||
|
'Main-Class': "furhatos.app.${lowerCasedName}.${normalizedName}Skill"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ShadowJar depends on jar being finished properly.
|
||||||
|
shadowJar {
|
||||||
|
manifest {
|
||||||
|
exclude '**/Log4j2Plugins.dat'
|
||||||
|
exclude '**/node_modules'
|
||||||
|
}
|
||||||
|
from "skill.properties"
|
||||||
|
from "assets"
|
||||||
|
extension 'skill'
|
||||||
|
}
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-bin.zip
|
||||||
172
gradlew
vendored
Normal file
172
gradlew
vendored
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
84
gradlew.bat
vendored
Normal file
84
gradlew.bat
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
15
skill.properties
Normal file
15
skill.properties
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
name = Blank
|
||||||
|
mainclass = furhatos.app.blank.BlankSkill
|
||||||
|
version = 1.0.0
|
||||||
|
language = en-US
|
||||||
|
logLevel = INFO
|
||||||
|
#You may set this to a Furhat version.
|
||||||
|
requiresVersion = false
|
||||||
|
#Set to true if this skill should fail if there is no camera
|
||||||
|
requiresCamera = false
|
||||||
|
#Set to true if this skill should fail if there is no speaker
|
||||||
|
requiresSpeaker = false
|
||||||
|
#Set to true if this skill should fail if there is no microphone
|
||||||
|
requiresMicrophone = false
|
||||||
|
#Set to true if this skill should fail if there is no active recognizer
|
||||||
|
requiresRecognizer = false
|
||||||
26
src/main/kotlin/furhatos/app/blank/flow/init.kt
Normal file
26
src/main/kotlin/furhatos/app/blank/flow/init.kt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package furhatos.app.blank.flow
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.main.Idle
|
||||||
|
import furhatos.app.blank.flow.main.supporting.SmileIdle
|
||||||
|
import furhatos.app.blank.setting.DISTANCE_TO_ENGAGE
|
||||||
|
import furhatos.app.blank.setting.MAX_NUMBER_OF_USERS
|
||||||
|
import furhatos.flow.kotlin.State
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.flow.kotlin.state
|
||||||
|
import furhatos.flow.kotlin.users
|
||||||
|
import furhatos.util.Gender
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
val Init: State = state {
|
||||||
|
init {
|
||||||
|
/** Set our default interaction parameters */
|
||||||
|
users.setSimpleEngagementPolicy(DISTANCE_TO_ENGAGE, MAX_NUMBER_OF_USERS)
|
||||||
|
furhat.setVoice(Language.SLOVAK, gender = Gender.FEMALE)
|
||||||
|
furhat.gesture(SmileIdle, async = true)
|
||||||
|
}
|
||||||
|
onEntry {
|
||||||
|
|
||||||
|
goto(Idle)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
433
src/main/kotlin/furhatos/app/blank/flow/main/SessionLogger.kt
Normal file
433
src/main/kotlin/furhatos/app/blank/flow/main/SessionLogger.kt
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
package furhatos.app.blank.flow.main
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.sendLogEvent
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.sendLogJsonLine
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.nio.file.StandardOpenOption
|
||||||
|
import java.util.UUID
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
object SessionLogger {
|
||||||
|
|
||||||
|
private val logsDir: Path = Paths.get(System.getProperty("user.dir"), "logs")
|
||||||
|
private val csvLogFile: Path = logsDir.resolve("sessions_log.csv")
|
||||||
|
private val pendingLogFile: Path = logsDir.resolve("pending_logs.jsonl")
|
||||||
|
private var sessionId: String = ""
|
||||||
|
|
||||||
|
private val pendingFileLock = Any()
|
||||||
|
private val flushInProgress = AtomicBoolean(false)
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (!Files.exists(logsDir)) {
|
||||||
|
Files.createDirectories(logsDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.exists(csvLogFile)) {
|
||||||
|
Files.write(
|
||||||
|
csvLogFile,
|
||||||
|
"timestamp;session_id;game;phase;question;attempt;result;correct_answer;user_answer;hint_used\n"
|
||||||
|
.toByteArray(Charsets.UTF_8),
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.APPEND
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.exists(pendingLogFile)) {
|
||||||
|
Files.createFile(pendingLogFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newSessionId(): String {
|
||||||
|
return UUID.randomUUID()
|
||||||
|
.toString()
|
||||||
|
.replace("-", "")
|
||||||
|
.take(6)
|
||||||
|
.uppercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun esc(value: String?): String {
|
||||||
|
if (value == null) return ""
|
||||||
|
return value
|
||||||
|
.replace("\"", "\"\"")
|
||||||
|
.replace("\n", " ")
|
||||||
|
.replace("\r", " ")
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun jsonEscape(value: String?): String {
|
||||||
|
if (value == null) return ""
|
||||||
|
return value
|
||||||
|
.replace("\\", "\\\\")
|
||||||
|
.replace("\"", "\\\"")
|
||||||
|
.replace("\n", "\\n")
|
||||||
|
.replace("\r", "\\r")
|
||||||
|
.replace("\t", "\\t")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildCsvLine(
|
||||||
|
ts: String,
|
||||||
|
sessionId: String,
|
||||||
|
game: String,
|
||||||
|
phase: String,
|
||||||
|
question: String,
|
||||||
|
attempt: Int,
|
||||||
|
result: String,
|
||||||
|
correctAnswer: String,
|
||||||
|
userAnswer: String?,
|
||||||
|
hintUsed: Boolean
|
||||||
|
): String {
|
||||||
|
return buildString {
|
||||||
|
append('"').append(esc(ts)).append('"').append(';')
|
||||||
|
append('"').append(esc(sessionId)).append('"').append(';')
|
||||||
|
append('"').append(esc(game)).append('"').append(';')
|
||||||
|
append('"').append(esc(phase)).append('"').append(';')
|
||||||
|
append('"').append(esc(question)).append('"').append(';')
|
||||||
|
append(attempt).append(';')
|
||||||
|
append('"').append(esc(result)).append('"').append(';')
|
||||||
|
append('"').append(esc(correctAnswer)).append('"').append(';')
|
||||||
|
append('"').append(esc(userAnswer)).append('"').append(';')
|
||||||
|
append(hintUsed)
|
||||||
|
append('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildJsonLine(
|
||||||
|
ts: String,
|
||||||
|
sessionId: String,
|
||||||
|
game: String,
|
||||||
|
phase: String,
|
||||||
|
question: String,
|
||||||
|
attempt: Int,
|
||||||
|
result: String,
|
||||||
|
correctAnswer: String,
|
||||||
|
userAnswer: String?,
|
||||||
|
hintUsed: Boolean
|
||||||
|
): String {
|
||||||
|
return buildString {
|
||||||
|
append("{")
|
||||||
|
append("\"created_at\":\"").append(jsonEscape(ts)).append("\",")
|
||||||
|
append("\"session_id\":\"").append(jsonEscape(sessionId)).append("\",")
|
||||||
|
append("\"game\":\"").append(jsonEscape(game)).append("\",")
|
||||||
|
append("\"phase\":\"").append(jsonEscape(phase)).append("\",")
|
||||||
|
append("\"question\":\"").append(jsonEscape(question)).append("\",")
|
||||||
|
append("\"attempt\":").append(attempt).append(",")
|
||||||
|
append("\"result\":\"").append(jsonEscape(result)).append("\",")
|
||||||
|
|
||||||
|
append("\"user_answer\":")
|
||||||
|
if (userAnswer.isNullOrBlank()) append("null")
|
||||||
|
else append("\"").append(jsonEscape(userAnswer)).append("\"")
|
||||||
|
append(",")
|
||||||
|
|
||||||
|
append("\"correct_answer\":")
|
||||||
|
if (correctAnswer.isBlank()) append("null")
|
||||||
|
else append("\"").append(jsonEscape(correctAnswer)).append("\"")
|
||||||
|
append(",")
|
||||||
|
|
||||||
|
append("\"hint_used\":").append(hintUsed)
|
||||||
|
append("}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW: all pending-file operations go through one lock
|
||||||
|
private fun readPendingLinesLocked(): List<String> {
|
||||||
|
if (!Files.exists(pendingLogFile)) return emptyList()
|
||||||
|
|
||||||
|
return Files.readAllLines(pendingLogFile, Charsets.UTF_8)
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW
|
||||||
|
private fun writePendingLinesLocked(lines: List<String>) {
|
||||||
|
val content =
|
||||||
|
if (lines.isEmpty()) ""
|
||||||
|
else lines.joinToString(separator = "\n", postfix = "\n")
|
||||||
|
|
||||||
|
Files.write(
|
||||||
|
pendingLogFile,
|
||||||
|
content.toByteArray(Charsets.UTF_8),
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.TRUNCATE_EXISTING
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun appendPendingJsonLine(jsonLine: String) {
|
||||||
|
synchronized(pendingFileLock) {
|
||||||
|
Files.write(
|
||||||
|
pendingLogFile,
|
||||||
|
(jsonLine + "\n").toByteArray(Charsets.UTF_8),
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.APPEND
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private fun appendPendingJsonLine(jsonLine: String) {
|
||||||
|
// Files.write(
|
||||||
|
// pendingLogFile,
|
||||||
|
// (jsonLine + "\n").toByteArray(Charsets.UTF_8),
|
||||||
|
// StandardOpenOption.CREATE,
|
||||||
|
// StandardOpenOption.APPEND
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Synchronized
|
||||||
|
// fun flushPendingLogs() {
|
||||||
|
// if (!Files.exists(pendingLogFile)) return
|
||||||
|
//
|
||||||
|
// val lines = Files.readAllLines(pendingLogFile, Charsets.UTF_8)
|
||||||
|
// .map { it.trim() }
|
||||||
|
// .filter { it.isNotBlank() }
|
||||||
|
//
|
||||||
|
// if (lines.isEmpty()) return
|
||||||
|
//
|
||||||
|
// val failedLines = mutableListOf<String>()
|
||||||
|
//
|
||||||
|
// for (line in lines) {
|
||||||
|
// val sent = try {
|
||||||
|
// sendLogJsonLine(line)
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// println("flushPendingLogs exception: ${e.message}")
|
||||||
|
// false
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!sent) {
|
||||||
|
// failedLines.add(line)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val newContent =
|
||||||
|
// if (failedLines.isEmpty()) ""
|
||||||
|
// else failedLines.joinToString(separator = "\n", postfix = "\n")
|
||||||
|
//
|
||||||
|
// Files.write(
|
||||||
|
// pendingLogFile,
|
||||||
|
// newContent.toByteArray(Charsets.UTF_8),
|
||||||
|
// StandardOpenOption.TRUNCATE_EXISTING
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// NEW: background flush
|
||||||
|
fun flushPendingLogsAsync() {
|
||||||
|
if (!flushInProgress.compareAndSet(false, true)) {
|
||||||
|
println("SessionLogger: flush already in progress, skip")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
thread(isDaemon = true, name = "pending-log-flush") {
|
||||||
|
try {
|
||||||
|
// 1) Take current batch and immediately free file for new records
|
||||||
|
val batch = synchronized(pendingFileLock) {
|
||||||
|
val lines = readPendingLinesLocked()
|
||||||
|
if (lines.isNotEmpty()) {
|
||||||
|
writePendingLinesLocked(emptyList())
|
||||||
|
}
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batch.isEmpty()) {
|
||||||
|
println("SessionLogger: no pending logs to flush")
|
||||||
|
return@thread
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Send outside file lock
|
||||||
|
val failedLines = mutableListOf<String>()
|
||||||
|
|
||||||
|
for (line in batch) {
|
||||||
|
val sent = try {
|
||||||
|
sendLogJsonLine(line)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("flushPendingLogsAsync exception: ${e.message}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sent) {
|
||||||
|
failedLines.add(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Put back only failed old lines + lines added meanwhile
|
||||||
|
synchronized(pendingFileLock) {
|
||||||
|
val freshLines = readPendingLinesLocked()
|
||||||
|
val merged = failedLines + freshLines
|
||||||
|
writePendingLinesLocked(merged)
|
||||||
|
}
|
||||||
|
|
||||||
|
println("SessionLogger: background flush finished, failed=${failedLines.size}")
|
||||||
|
} finally {
|
||||||
|
flushInProgress.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun startNewSession() {
|
||||||
|
sessionId = newSessionId()
|
||||||
|
|
||||||
|
log(
|
||||||
|
game = "system",
|
||||||
|
phase = "session",
|
||||||
|
question = "greeting",
|
||||||
|
attempt = 0,
|
||||||
|
result = "session_started",
|
||||||
|
correctAnswer = "",
|
||||||
|
userAnswer = "",
|
||||||
|
hintUsed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun log(
|
||||||
|
game: String,
|
||||||
|
phase: String,
|
||||||
|
question: String,
|
||||||
|
attempt: Int,
|
||||||
|
result: String,
|
||||||
|
correctAnswer: String = "",
|
||||||
|
userAnswer: String? = "",
|
||||||
|
hintUsed: Boolean = false
|
||||||
|
) {
|
||||||
|
if (sessionId.isBlank()) {
|
||||||
|
sessionId = newSessionId()
|
||||||
|
}
|
||||||
|
|
||||||
|
val ts = OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
|
||||||
|
// val ts = OffsetDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||||
|
|
||||||
|
val csvLine = buildCsvLine(
|
||||||
|
ts = ts,
|
||||||
|
sessionId = sessionId,
|
||||||
|
game = game,
|
||||||
|
phase = phase,
|
||||||
|
question = question,
|
||||||
|
attempt = attempt,
|
||||||
|
result = result,
|
||||||
|
correctAnswer = correctAnswer,
|
||||||
|
userAnswer = userAnswer,
|
||||||
|
hintUsed = hintUsed
|
||||||
|
)
|
||||||
|
|
||||||
|
val jsonLine = buildJsonLine(
|
||||||
|
ts = ts,
|
||||||
|
sessionId = sessionId,
|
||||||
|
game = game,
|
||||||
|
phase = phase,
|
||||||
|
question = question,
|
||||||
|
attempt = attempt,
|
||||||
|
result = result,
|
||||||
|
correctAnswer = correctAnswer,
|
||||||
|
userAnswer = userAnswer,
|
||||||
|
hintUsed = hintUsed
|
||||||
|
)
|
||||||
|
|
||||||
|
// локальный архив
|
||||||
|
if (shouldWriteCsv()) {
|
||||||
|
Files.write(
|
||||||
|
csvLogFile,
|
||||||
|
csvLine.toByteArray(Charsets.UTF_8),
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.APPEND
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
println("SessionLogger: CSV logging skipped because skill is running on robot")
|
||||||
|
}
|
||||||
|
// Files.write(
|
||||||
|
// csvLogFile,
|
||||||
|
// csvLine.toByteArray(Charsets.UTF_8),
|
||||||
|
// StandardOpenOption.CREATE,
|
||||||
|
// StandardOpenOption.APPEND
|
||||||
|
// )
|
||||||
|
|
||||||
|
// потом текущую
|
||||||
|
val sent = try {
|
||||||
|
sendLogEvent(
|
||||||
|
createdAt = ts,
|
||||||
|
sessionId = sessionId,
|
||||||
|
game = game,
|
||||||
|
phase = phase,
|
||||||
|
question = question,
|
||||||
|
attempt = attempt,
|
||||||
|
result = result,
|
||||||
|
userAnswer = userAnswer?.ifBlank { null },
|
||||||
|
correctAnswer = correctAnswer.ifBlank { null },
|
||||||
|
hintUsed = hintUsed
|
||||||
|
)
|
||||||
|
// sendLogJsonLine(jsonLine)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("SessionLogger remote send exception: ${e.message}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sent) {
|
||||||
|
appendPendingJsonLine(jsonLine)
|
||||||
|
println("SessionLogger: remote logging failed, saved to pending_logs.jsonl")
|
||||||
|
} else {
|
||||||
|
println("SessionLogger: remote logging success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isLocalProjectRun(): Boolean {
|
||||||
|
val userDir = Paths.get(System.getProperty("user.dir"))
|
||||||
|
|
||||||
|
val localMarkers = listOf(
|
||||||
|
".idea",
|
||||||
|
"src",
|
||||||
|
"build.gradle",
|
||||||
|
"settings.gradle",
|
||||||
|
"gradlew",
|
||||||
|
"gradlew.bat"
|
||||||
|
)
|
||||||
|
|
||||||
|
return localMarkers.any { marker ->
|
||||||
|
Files.exists(userDir.resolve(marker))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isRunningOnRobot(): Boolean = !isLocalProjectRun()
|
||||||
|
|
||||||
|
fun shouldWriteCsv(): Boolean = isLocalProjectRun()
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun clearCsvIfRunningOnRobot() {
|
||||||
|
if (!isRunningOnRobot()) return
|
||||||
|
if (!Files.exists(csvLogFile)) return
|
||||||
|
|
||||||
|
Files.write(
|
||||||
|
csvLogFile,
|
||||||
|
"timestamp;session_id;game;phase;question;attempt;result;correct_answer;user_answer;hint_used\n"
|
||||||
|
.toByteArray(Charsets.UTF_8),
|
||||||
|
StandardOpenOption.TRUNCATE_EXISTING
|
||||||
|
)
|
||||||
|
|
||||||
|
println("SessionLogger: CSV cleared because skill is running on robot")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSessionId(): String = sessionId
|
||||||
|
}
|
||||||
|
|
||||||
|
//==== test ======
|
||||||
|
fun pendingFileStatusMessage(): String {
|
||||||
|
return try {
|
||||||
|
val file = Paths.get(System.getProperty("user.dir"), "logs", "pending_logs.jsonl")
|
||||||
|
|
||||||
|
if (!Files.exists(file)) {
|
||||||
|
return "Pending súbor neexistuje."
|
||||||
|
}
|
||||||
|
|
||||||
|
val lines = Files.readAllLines(file, Charsets.UTF_8)
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
|
||||||
|
when {
|
||||||
|
lines.isEmpty() -> "Pending súbor je prázdny."
|
||||||
|
else -> "Pending súbor obsahuje ${lines.size} záznamov."
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"Nepodarilo sa skontrolovať pending súbor."
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package furhatos.app.blank.flow.main
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.attention.AttentionTrainingIntro
|
||||||
|
import furhatos.flow.kotlin.State
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.app.blank.flow.main.say_time.TimeTrainingIntro
|
||||||
|
import furhatos.app.blank.flow.main.memory.MemoryTrainingIntro
|
||||||
|
import furhatos.app.blank.flow.main.say_time.resetTimeQuestions
|
||||||
|
import furhatos.app.blank.flow.main.supporting.calm
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
// 1 = TimeTrainingIntro, 2 = MemoryTrainingIntro, 3 = AttentionTrainingIntro
|
||||||
|
|
||||||
|
private var gameBag: MutableList<Int> = mutableListOf()
|
||||||
|
|
||||||
|
private fun nextGameFromBag(): Int {
|
||||||
|
if (gameBag.isEmpty()) {
|
||||||
|
gameBag = mutableListOf(1, 2, 3).also { it.shuffle() }
|
||||||
|
}
|
||||||
|
return gameBag.removeAt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset bag for a new user/session
|
||||||
|
fun resetTrainingGameBag() {
|
||||||
|
gameBag.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
val StartQuestion: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
calm()
|
||||||
|
when (nextGameFromBag()) {
|
||||||
|
1 -> {
|
||||||
|
furhat.say("Poďme zahrať hru na orientáciu v čase.")
|
||||||
|
furhat.say("Budem Vám klásť rôzne otázky a Vašou úlohou bude správne pomenovať aktuálny čas.")
|
||||||
|
delay(1000)
|
||||||
|
resetTimeQuestions()
|
||||||
|
goto(TimeTrainingIntro)
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
// furhat.say("Poviem Vám zoznam slov a Vašou úlohou bude ho zopakovať.")
|
||||||
|
furhat.say("Poďme zahrať hru na pamäť.")
|
||||||
|
delay(1000)
|
||||||
|
resetTimeQuestions()
|
||||||
|
goto(MemoryTrainingIntro)
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
furhat.say("Poďme zahrať hru na pozornosť.")
|
||||||
|
furhat.say("Budem hovoriť slová a pri jednom z nich ma budete musieť zastaviť.")
|
||||||
|
delay(1000)
|
||||||
|
resetTimeQuestions()
|
||||||
|
goto(AttentionTrainingIntro)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
106
src/main/kotlin/furhatos/app/blank/flow/main/attention/AST.kt
Normal file
106
src/main/kotlin/furhatos/app/blank/flow/main/attention/AST.kt
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package furhatos.app.blank.flow.main.attention
|
||||||
|
|
||||||
|
object AttentionSmallTalkPhrases {
|
||||||
|
|
||||||
|
fun questionFor(theme: String, target: String): String {
|
||||||
|
val byTheme = when (theme.lowercase()) {
|
||||||
|
"ovocie" -> listOf(
|
||||||
|
"Povedali ste stop pri slove „$target“. Máte radi ovocie?",
|
||||||
|
"Aké ovocie vám chutí najviac?",
|
||||||
|
"Dávate si ovocie skôr ráno, alebo počas dňa?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"sladké" -> listOf(
|
||||||
|
"Povedali ste stop pri slove „$target“. Máte radi sladké?",
|
||||||
|
"Doprajete si občas niečo sladké, alebo skôr nie?",
|
||||||
|
"Ak by ste si mali vybrať niečo sladké, čo by to bolo?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"zelenina" -> listOf(
|
||||||
|
"„$target“ je zelenina. Máte radi zeleninu?",
|
||||||
|
"Ktorú zeleninu máte najradšej?",
|
||||||
|
"Jedávate zeleninu skôr surovú, alebo varenú?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"jedlo" -> listOf(
|
||||||
|
"Slovo „$target“ patrí medzi jedlo. Máte nejaké jedlo, ktoré máte obzvlášť radi?",
|
||||||
|
"Keď sa povie „$target“, napadne vám naň chuť?",
|
||||||
|
"Varíte si radšej doma, alebo máte radi aj jedálne či reštaurácie?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"bobuľa" -> listOf(
|
||||||
|
"„$target“ patrí medzi bobule. Máte radi bobuľové ovocie?",
|
||||||
|
"Spájate si „$target“ skôr s letom, alebo si ho dávate aj počas roka?",
|
||||||
|
"Máte radšej čerstvé bobule, alebo napríklad v koláči či jogurte?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"nápoje" -> listOf(
|
||||||
|
"„$target“ patrí medzi nápoje. Čo najčastejšie pijete počas dňa?",
|
||||||
|
"Máte radšej teplé nápoje alebo studené?",
|
||||||
|
"Keď si chcete oddýchnuť, aký nápoj vám spraví najväčšiu radosť?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"prísady" -> listOf(
|
||||||
|
"„$target“ patrí medzi prísady. Radi varíte alebo pečiete?",
|
||||||
|
"Používate pri varení skôr jednoduché prísady, alebo radi skúšate nové veci?",
|
||||||
|
"Máte nejakú prísadu, bez ktorej si varenie neviete predstaviť?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"dom" -> listOf(
|
||||||
|
"Slovo „$target“ patrí k téme domov. Máte doma nejaké miesto, kde sa cítite najlepšie?",
|
||||||
|
"Máte radšej, keď je doma ticho, alebo keď je tam „živo“?",
|
||||||
|
"Keď si chcete oddýchnuť doma, čo vám najviac pomáha?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"domáce potreby" -> listOf(
|
||||||
|
"„$target“ patrí medzi domáce potreby. Máte doma veci, ktoré používate každý deň?",
|
||||||
|
"Máte radi poriadok a systém, alebo skôr „prirodzený chaos“?",
|
||||||
|
"Keď niečo v domácnosti chýba, všimnete si to hneď?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"technika" -> listOf(
|
||||||
|
"„$target“ patrí medzi techniku. Používate techniku radi, alebo len keď treba?",
|
||||||
|
"Ktoré zariadenie vám doma najviac uľahčuje život?",
|
||||||
|
"Skôr máte radi jednoduché veci, alebo vás baví skúšať nové technológie?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"príroda" -> listOf(
|
||||||
|
"„$target“ patrí k prírode. Máte radi pobyt vonku?",
|
||||||
|
"Čo vám je v prírode príjemnejšie — les, voda, alebo hory?",
|
||||||
|
"Chodíte radšej na krátke prechádzky, alebo na dlhšie výlety?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"mesto" -> listOf(
|
||||||
|
"„$target“ patrí k téme mesto. Máte radi ruch mesta, alebo skôr pokoj?",
|
||||||
|
"Keď ste v meste, čo je pre vás najdôležitejšie — doprava, služby, alebo atmosféra?",
|
||||||
|
"Máte v meste nejaké obľúbené miesto, kam sa radi vraciate?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"telo" -> listOf(
|
||||||
|
"„$target“ súvisí s telom. Venujete sa počas dňa aspoň trochu pohybu?",
|
||||||
|
"Máte radi, keď je deň aktívny, alebo skôr pokojný?",
|
||||||
|
"Čo vám najviac pomáha cítiť sa dobre — prechádzka, oddych, alebo rutina?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"oblečenie" -> listOf(
|
||||||
|
"„$target“ patrí k oblečeniu. Máte radi pohodlné oblečenie, alebo skôr elegantné?",
|
||||||
|
"Vyberáte si oblečenie podľa počasia, alebo skôr podľa nálady?",
|
||||||
|
"Máte nejakú farbu, ktorú nosíte najradšej?"
|
||||||
|
)
|
||||||
|
|
||||||
|
"druh zábavy" -> listOf(
|
||||||
|
"„$target“ patrí k zábave. Ako najradšej trávite voľný čas?",
|
||||||
|
"Máte radšej spoločnú zábavu s ľuďmi, alebo pokojnejšie aktivity?",
|
||||||
|
"Keď si chcete oddýchnuť, čo vás poteší najviac?"
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> listOf(
|
||||||
|
"Zachytili ste slovo „$target“. Je to téma „$theme“. Máte k tomu nejaký vzťah?",
|
||||||
|
"„$target“ — je to z oblasti „$theme“. Páči sa vám táto téma, alebo skôr nie?",
|
||||||
|
"Keď počujete „$target“, napadne vám niečo príjemné, alebo skôr neutrálne?"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return byTheme.random()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,634 @@
|
|||||||
|
package furhatos.app.blank.flow.main.attention
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.SessionLogger
|
||||||
|
import furhatos.app.blank.flow.main.StartQuestion
|
||||||
|
import furhatos.app.blank.flow.main.handlers.Goodbye
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRepeat
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleStop
|
||||||
|
import furhatos.app.blank.flow.main.supporting.AskToContinue
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.WordBank
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.WordEntry
|
||||||
|
import furhatos.app.blank.flow.main.supporting.empathy
|
||||||
|
import furhatos.app.blank.flow.main.supporting.littleSad
|
||||||
|
import furhatos.app.blank.flow.main.supporting.veryHappy
|
||||||
|
import furhatos.app.blank.flow.main.supporting.AskIncreaseDifficulty
|
||||||
|
import furhatos.app.blank.flow.main.memory.WordsChecker
|
||||||
|
import furhatos.app.blank.flow.main.supporting.AskDecreaseDifficulty
|
||||||
|
import furhatos.app.blank.flow.main.supporting.ReadyToTrain
|
||||||
|
import furhatos.app.blank.flow.main.supporting.SmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.SmallTalkContext
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.TrainingMenuFlags
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.callProxyRespond
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.genericSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.isProxyAvailable
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.requestSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.happyNod
|
||||||
|
import furhatos.app.blank.flow.main.supporting.happyShake
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Repeat
|
||||||
|
import furhatos.app.blank.nlu.base_answer.StopTraining
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import furhatos.gestures.Gestures
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
// -------------------- parametre --------------------
|
||||||
|
|
||||||
|
// na sledovanie toku otázok
|
||||||
|
private const val MAX_LEVEL = 3
|
||||||
|
private const val QUESTIONS_PER_LEVEL = 2
|
||||||
|
private const val MAX_SMALLTALK_PER_SESSION = 3
|
||||||
|
private const val MAX_SAME_LEVEL_QUESTIONS = 6
|
||||||
|
|
||||||
|
var sameLevelQuestionsDone: Int = 0
|
||||||
|
var difficultyWasIncreased: Boolean = false
|
||||||
|
|
||||||
|
// koľko kôl v aktuálnom bloku z 2 bolo neúspešných
|
||||||
|
var failedQuestions: Int = 0
|
||||||
|
|
||||||
|
// Bol aktuálny blok z 2 kôl začatý po zvýšení náročnosti
|
||||||
|
var currentBlockWasIncreased: Boolean = false
|
||||||
|
|
||||||
|
// príznak výsledku aktuálneho kola pre AfterAttentionResolved
|
||||||
|
private var attentionRoundFailed: Boolean = false
|
||||||
|
|
||||||
|
// pomocne premenne pre SmallTak
|
||||||
|
var attentionSmallTalkUsed = 0
|
||||||
|
var lastAttentionTheme: String = ""
|
||||||
|
var lastAttentionTarget: String = ""
|
||||||
|
|
||||||
|
// dĺžka radu slov rastie s úrovňou
|
||||||
|
private fun lengthForLevel(level: Int, rng: Random = Random.Default): Int = when (level) {
|
||||||
|
1 -> listOf(4, 5).random(rng)
|
||||||
|
2 -> listOf(8, 9).random(rng)
|
||||||
|
else -> listOf(10, 11).random(rng)
|
||||||
|
}
|
||||||
|
|
||||||
|
// okno čakania na „stop“ po každom slove
|
||||||
|
private fun listenWindowForLevel(level: Int): Long = when (level) {
|
||||||
|
1 -> 3800
|
||||||
|
2 -> 3500
|
||||||
|
else -> 3200
|
||||||
|
// 1 -> 1800
|
||||||
|
// 2 -> 1500
|
||||||
|
// else -> 1200
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- stav sedenia --------------------
|
||||||
|
|
||||||
|
var attentionLevel: Int = 1
|
||||||
|
var doneAtLevel: Int = 0
|
||||||
|
|
||||||
|
private var wrongAttempts: Int = 0
|
||||||
|
|
||||||
|
private var targetWord: WordEntry? = null
|
||||||
|
private var targetIndex: Int = -1
|
||||||
|
private var sequence: List<WordEntry> = emptyList()
|
||||||
|
private var currentIndex: Int = 0
|
||||||
|
|
||||||
|
// -------------------- generovanie zoznamu --------------------
|
||||||
|
|
||||||
|
private fun buildNewAttentionRound() {
|
||||||
|
val len = lengthForLevel(attentionLevel)
|
||||||
|
|
||||||
|
// target sa vyberá podľa úrovne
|
||||||
|
val target = WordBank.pickSequence(length = 1, maxDifficulty = attentionLevel).first()
|
||||||
|
|
||||||
|
// vyberáju sa ostatné slovaokrem target
|
||||||
|
val others = mutableListOf<WordEntry>()
|
||||||
|
while (others.size < len - 1) {
|
||||||
|
val cand = WordBank.pickSequence(length = 1, maxDifficulty = attentionLevel).first()
|
||||||
|
if (cand.canonical != target.canonical && others.none { it.canonical == cand.canonical }) {
|
||||||
|
others.add(cand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// vkladánie target do náhodnej pozície
|
||||||
|
val insertAt = Random.nextInt(0, len)
|
||||||
|
val mixed = others.toMutableList()
|
||||||
|
mixed.add(insertAt, target)
|
||||||
|
|
||||||
|
targetWord = target
|
||||||
|
targetIndex = insertAt
|
||||||
|
sequence = mixed
|
||||||
|
|
||||||
|
currentIndex = 0
|
||||||
|
wrongAttempts = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- helpery --------------------
|
||||||
|
|
||||||
|
private fun userSaidStop(text: String?): Boolean {
|
||||||
|
val t = text ?: return false
|
||||||
|
val tokens = WordsChecker.tokenizeMeaningful(t)
|
||||||
|
return tokens.any { it == "stop" }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun FlowControlRunner.restartSameRound() {
|
||||||
|
currentIndex = 0
|
||||||
|
// sequence/target остаются те же, чтобы попытка была честной
|
||||||
|
goto(SayNextWord)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestAttentionExplainWrong(mistakeType: String): String {
|
||||||
|
val target = targetWord?.canonical ?: ""
|
||||||
|
val position = targetIndex + 1
|
||||||
|
|
||||||
|
if (!isProxyAvailable()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return callProxyRespond(
|
||||||
|
userText = "Používateľ urobil chybu v cvičení pozornosti.",
|
||||||
|
task = "explain_wrong",
|
||||||
|
context = mapOf(
|
||||||
|
"exercise" to "attention",
|
||||||
|
"mistake_type" to mistakeType, // early_stop / missed_target / wrong_word
|
||||||
|
"target_word" to target,
|
||||||
|
"target_position" to position,
|
||||||
|
"theme" to (targetWord?.theme ?: ""),
|
||||||
|
"level" to attentionLevel
|
||||||
|
)
|
||||||
|
) ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// Hint
|
||||||
|
//==============================================================
|
||||||
|
fun requestAttentionHint(mistakeType: String): String {
|
||||||
|
val target = targetWord?.canonical ?: ""
|
||||||
|
|
||||||
|
if (!isProxyAvailable()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return callProxyRespond(
|
||||||
|
userText = "Používateľ potrebuje nápovedu.",
|
||||||
|
task = "hint",
|
||||||
|
context = mapOf(
|
||||||
|
"exercise" to "attention",
|
||||||
|
"mistake_type" to mistakeType, // early_stop / missed_target
|
||||||
|
"target_word" to target,
|
||||||
|
"theme" to (targetWord?.theme ?: ""),
|
||||||
|
"level" to attentionLevel
|
||||||
|
)
|
||||||
|
) ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// Small Talk
|
||||||
|
//==============================================================
|
||||||
|
val AttentionThemeSmallTalk: State by lazy {
|
||||||
|
SmallTalk(nextState = AfterAttentionResolved) {
|
||||||
|
AttentionSmallTalkPhrases.questionFor(lastAttentionTheme, lastAttentionTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextStateAfterCorrectAttention(): State {
|
||||||
|
if (attentionSmallTalkUsed >= MAX_SMALLTALK_PER_SESSION) return AfterAttentionResolved
|
||||||
|
|
||||||
|
// šanca dostať dodatočnú otázku
|
||||||
|
val totalPlanned = MAX_LEVEL * QUESTIONS_PER_LEVEL // условные 6 заданий
|
||||||
|
val progress = (attentionLevel - 1) * QUESTIONS_PER_LEVEL + doneAtLevel
|
||||||
|
val denom = (totalPlanned - progress).coerceAtLeast(1)
|
||||||
|
|
||||||
|
val smallTalkLeft = MAX_SMALLTALK_PER_SESSION - attentionSmallTalkUsed
|
||||||
|
val percent = minOf(0.5, smallTalkLeft.toDouble() / denom.toDouble())
|
||||||
|
|
||||||
|
val doSmallTalk = Random.nextDouble() < percent
|
||||||
|
if (!doSmallTalk) return AfterAttentionResolved
|
||||||
|
|
||||||
|
attentionSmallTalkUsed++
|
||||||
|
return AttentionThemeSmallTalk
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildSmallTalkContextAttention(): SmallTalkContext {
|
||||||
|
return SmallTalkContext(
|
||||||
|
exercise = "attention",
|
||||||
|
topic = lastAttentionTheme,
|
||||||
|
subtopic = "theme_question",
|
||||||
|
targetWord = lastAttentionTarget,
|
||||||
|
responseMode = "open"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun smallTalkManagerAttention(): State {
|
||||||
|
if (attentionSmallTalkUsed >= MAX_SMALLTALK_PER_SESSION) return AfterAttentionResolved
|
||||||
|
|
||||||
|
val totalPlanned = MAX_LEVEL * QUESTIONS_PER_LEVEL
|
||||||
|
val progress = (attentionLevel - 1) * QUESTIONS_PER_LEVEL + doneAtLevel
|
||||||
|
val denom = (totalPlanned - progress).coerceAtLeast(1)
|
||||||
|
|
||||||
|
val smallTalkLeft = MAX_SMALLTALK_PER_SESSION - attentionSmallTalkUsed
|
||||||
|
val percent = minOf(0.5, smallTalkLeft.toDouble() / denom.toDouble())
|
||||||
|
|
||||||
|
val doSmallTalk = Random.nextDouble() < percent
|
||||||
|
if (!doSmallTalk) return AfterAttentionResolved
|
||||||
|
|
||||||
|
val ctx = buildSmallTalkContextAttention()
|
||||||
|
val proxyQuestion = requestSmallTalk(ctx)
|
||||||
|
|
||||||
|
attentionSmallTalkUsed++
|
||||||
|
|
||||||
|
return if (proxyQuestion.isNotBlank()) {
|
||||||
|
genericSmallTalk(
|
||||||
|
context = ctx,
|
||||||
|
nextState = AfterAttentionResolved,
|
||||||
|
preparedQuestion = proxyQuestion
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AttentionThemeSmallTalk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// Proces hry
|
||||||
|
//==============================================================
|
||||||
|
|
||||||
|
// Intro
|
||||||
|
val AttentionTrainingIntro: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
attentionLevel = 1
|
||||||
|
doneAtLevel = 0
|
||||||
|
attentionSmallTalkUsed = 0
|
||||||
|
|
||||||
|
sameLevelQuestionsDone = 0
|
||||||
|
difficultyWasIncreased = false
|
||||||
|
|
||||||
|
failedQuestions = 0
|
||||||
|
currentBlockWasIncreased = false
|
||||||
|
attentionRoundFailed = false
|
||||||
|
|
||||||
|
buildNewAttentionRound()
|
||||||
|
goto(AskAttention)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inštrukcia a začiatok
|
||||||
|
val AskAttention: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
val target = targetWord ?: run {
|
||||||
|
buildNewAttentionRound()
|
||||||
|
targetWord!!
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1000)
|
||||||
|
furhat.say("Pripravte sa.")
|
||||||
|
furhat.say("Keď budete počuť slovo „${target.canonical}“, povedzte prosím: stop.")
|
||||||
|
delay(1000)
|
||||||
|
furhat.say("Začíname.")
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "attention",
|
||||||
|
phase = "question_shown",
|
||||||
|
question = "LEVEL_${attentionLevel}",
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "asked",
|
||||||
|
correctAnswer = "${target.canonical}@${targetIndex + 1}",
|
||||||
|
userAnswer = "",
|
||||||
|
hintUsed = wrongAttempts > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
goto(SayNextWord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ďalšie iné slovo
|
||||||
|
val SayNextWord: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
if (currentIndex >= sequence.size) {
|
||||||
|
goto(HandleMissedStop)
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
val w = sequence[currentIndex]
|
||||||
|
furhat.say(w.canonical)
|
||||||
|
|
||||||
|
// сразу после слова — короткое окно "молчаливого слушания"
|
||||||
|
goto(ListenForStop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// počuvanie stop
|
||||||
|
val ListenForStop: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
furhat.param.endSilTimeout = 350
|
||||||
|
furhat.param.noSpeechTimeout = listenWindowForLevel(attentionLevel).toInt()
|
||||||
|
|
||||||
|
furhat.attend(users.current)
|
||||||
|
furhat.listen()
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<StopTraining> {
|
||||||
|
if (handleStop(it.intent,
|
||||||
|
"attention",
|
||||||
|
"LEVEL_${attentionLevel}",
|
||||||
|
it.text ?: ""))
|
||||||
|
{
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// onResponse<Repeat>{
|
||||||
|
// if (handleRepeat(
|
||||||
|
// it.intent,
|
||||||
|
// "attention",
|
||||||
|
// "LEVEL_${attentionLevel}",
|
||||||
|
// "${targetWord?.canonical}@${targetIndex + 1}",
|
||||||
|
// it.text ?: "",
|
||||||
|
// true
|
||||||
|
// )) {
|
||||||
|
// return@onResponse
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
if (userSaidStop(it.text)) {
|
||||||
|
val isCorrectMoment = (currentIndex == targetIndex)
|
||||||
|
|
||||||
|
if (isCorrectMoment) {
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "attention",
|
||||||
|
phase = "answer",
|
||||||
|
question = "LEVEL_${attentionLevel}",
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "success",
|
||||||
|
correctAnswer = "${targetWord?.canonical}@${targetIndex + 1}",
|
||||||
|
userAnswer = it.text ?: "",
|
||||||
|
hintUsed = wrongAttempts > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
furhat.gesture(Gestures.Nod, async = true)
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Výborne! Správne ste zareagovali.")
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
|
val t = targetWord!!
|
||||||
|
lastAttentionTheme = t.theme
|
||||||
|
lastAttentionTarget = t.canonical
|
||||||
|
|
||||||
|
attentionRoundFailed = false
|
||||||
|
|
||||||
|
// goto(nextStateAfterCorrectAttention())
|
||||||
|
goto(smallTalkManagerAttention())
|
||||||
|
} else {
|
||||||
|
// 'stop' na inom slove
|
||||||
|
wrongAttempts++
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "attention",
|
||||||
|
phase = "answer",
|
||||||
|
question = "LEVEL_${attentionLevel}",
|
||||||
|
attempt = wrongAttempts,
|
||||||
|
result = "early_stop",
|
||||||
|
correctAnswer = "${targetWord?.canonical}@${targetIndex + 1}",
|
||||||
|
userAnswer = it.text ?: "",
|
||||||
|
hintUsed = wrongAttempts >= 2
|
||||||
|
)
|
||||||
|
|
||||||
|
when (wrongAttempts) {
|
||||||
|
1 -> {
|
||||||
|
littleSad()
|
||||||
|
furhat.say("Zastavili ste ma pri inom slove.")
|
||||||
|
|
||||||
|
val hint = requestAttentionHint("early_stop")
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "attention",
|
||||||
|
phase = "event",
|
||||||
|
question = "LEVEL_${attentionLevel}",
|
||||||
|
attempt = wrongAttempts,
|
||||||
|
result = "hint_requested_early_stop",
|
||||||
|
correctAnswer = "${targetWord?.canonical}@${targetIndex + 1}",
|
||||||
|
userAnswer = it.text ?: "",
|
||||||
|
hintUsed = true
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hint.isNotBlank()){
|
||||||
|
furhat.say("Trochu pomôžem Vám! Dajte mi chvíľu, prosím")
|
||||||
|
furhat.say(hint)
|
||||||
|
delay(1300)
|
||||||
|
}else{
|
||||||
|
happyShake()
|
||||||
|
furhat.say("Nevadí.")
|
||||||
|
furhat.say("Skúsme to ešte raz. Povedzte stop až keď zaznie správne slovo.")
|
||||||
|
}
|
||||||
|
|
||||||
|
restartSameRound()
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
val explanation = requestAttentionExplainWrong("early_stop")
|
||||||
|
val target = targetWord!!.canonical
|
||||||
|
|
||||||
|
if (explanation.isNotBlank()){
|
||||||
|
furhat.say(explanation)
|
||||||
|
}else{
|
||||||
|
empathy()
|
||||||
|
furhat.say("Pomôžem Vám. Správne slovo je „$target“. Skúsme znova.")
|
||||||
|
}
|
||||||
|
|
||||||
|
restartSameRound()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
goto(FinalWrongAnswer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// používateľ povedal niečo iné — ignorovať
|
||||||
|
currentIndex++
|
||||||
|
goto(SayNextWord)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onNoResponse {
|
||||||
|
currentIndex++
|
||||||
|
goto(SayNextWord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- nebolo žiadného stop --------------------
|
||||||
|
|
||||||
|
val HandleMissedStop: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
wrongAttempts++
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "attention",
|
||||||
|
phase = "answer",
|
||||||
|
question = "LEVEL_${attentionLevel}",
|
||||||
|
attempt = wrongAttempts,
|
||||||
|
result = "missed_target",
|
||||||
|
correctAnswer = "${targetWord?.canonical}@${targetIndex + 1}",
|
||||||
|
userAnswer = "",
|
||||||
|
hintUsed = wrongAttempts >= 2
|
||||||
|
)
|
||||||
|
|
||||||
|
when (wrongAttempts) {
|
||||||
|
1 -> {
|
||||||
|
littleSad()
|
||||||
|
furhat.say("Zmeškali ste správny moment, keď ste ma mali zastaviť.")
|
||||||
|
|
||||||
|
val hint = requestAttentionHint("missed_target")
|
||||||
|
|
||||||
|
if (hint.isNotBlank()){
|
||||||
|
furhat.say("Trochu pomôžem Vám! Dajte mi chvíľu, prosím")
|
||||||
|
furhat.say(hint)
|
||||||
|
}else{
|
||||||
|
happyShake()
|
||||||
|
furhat.say("Nevadí.")
|
||||||
|
furhat.say("Skúsme to ešte raz. Keď zaznie správne slovo, povedzte stop.")
|
||||||
|
}
|
||||||
|
|
||||||
|
restartSameRound()
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
val explanation = requestAttentionExplainWrong("missed_target")
|
||||||
|
val target = targetWord!!.canonical
|
||||||
|
|
||||||
|
if (explanation.isNotBlank()){
|
||||||
|
furhat.say(explanation)
|
||||||
|
}else{
|
||||||
|
happyNod()
|
||||||
|
furhat.say("Pomôžem Vám. Správne slovo bolo „$target“. Skúsme to znova.")
|
||||||
|
}
|
||||||
|
|
||||||
|
restartSameRound()
|
||||||
|
}
|
||||||
|
else -> goto(FinalWrongAnswer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------- 3. chyba -------------------- */
|
||||||
|
|
||||||
|
val FinalWrongAnswer: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
empathy()
|
||||||
|
|
||||||
|
val target = targetWord!!.canonical
|
||||||
|
val position = targetIndex + 1
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "attention",
|
||||||
|
phase = "resolved",
|
||||||
|
question = "LEVEL_${attentionLevel}",
|
||||||
|
attempt = wrongAttempts,
|
||||||
|
result = "final_fail",
|
||||||
|
correctAnswer = "$target@$position",
|
||||||
|
userAnswer = "",
|
||||||
|
hintUsed = true
|
||||||
|
)
|
||||||
|
|
||||||
|
furhat.say("Tentoraz to nevyšlo,")
|
||||||
|
happyShake()
|
||||||
|
furhat.say("ale nič sa nedeje.")
|
||||||
|
furhat.say("Povedať stop ste mali pri slove „$target“, ktoré bolo na pozícii $position .")
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
|
attentionRoundFailed = true
|
||||||
|
|
||||||
|
goto(AfterAttentionResolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------- prechod medzi urovni -------------------- */
|
||||||
|
|
||||||
|
val AfterAttentionResolved: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
doneAtLevel++
|
||||||
|
|
||||||
|
if (attentionRoundFailed) {
|
||||||
|
failedQuestions++
|
||||||
|
}
|
||||||
|
attentionRoundFailed = false
|
||||||
|
|
||||||
|
|
||||||
|
if (!difficultyWasIncreased) {
|
||||||
|
sameLevelQuestionsDone++
|
||||||
|
|
||||||
|
if (sameLevelQuestionsDone >= MAX_SAME_LEVEL_QUESTIONS) {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Výborne, dnes už stačí. Ukončíme toto cvičenie pozornosti. Ďakujem Vám!")
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (doneAtLevel < QUESTIONS_PER_LEVEL) {
|
||||||
|
buildNewAttentionRound()
|
||||||
|
goto(AskAttention)
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// max -> koniec
|
||||||
|
if (attentionLevel >= MAX_LEVEL) {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Týmto sme ukončili hru pozornosti. Ďakujem Vám!")
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
|
TrainingMenuFlags.allAttentionQuestionsCompleted = true
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// pokaračovanie
|
||||||
|
val wantsToContinue = call(AskToContinue()) as Boolean
|
||||||
|
if (!wantsToContinue) {
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 otázky neúspešné -> znižiť uroveň
|
||||||
|
if (currentBlockWasIncreased &&
|
||||||
|
failedQuestions >= QUESTIONS_PER_LEVEL &&
|
||||||
|
attentionLevel > 1
|
||||||
|
) {
|
||||||
|
val goBack = call(AskDecreaseDifficulty) as Boolean
|
||||||
|
|
||||||
|
if (goBack) {
|
||||||
|
attentionLevel--
|
||||||
|
furhat.say("Dobre, vrátime sa na ľahšiu úroveň.")
|
||||||
|
} else {
|
||||||
|
furhat.say("Dobre, zostaneme na rovnakej úrovni.")
|
||||||
|
}
|
||||||
|
|
||||||
|
doneAtLevel = 0
|
||||||
|
failedQuestions = 0
|
||||||
|
currentBlockWasIncreased = false
|
||||||
|
|
||||||
|
buildNewAttentionRound()
|
||||||
|
goto(AskAttention)
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// zväčšiť uroveň
|
||||||
|
val increase = call(AskIncreaseDifficulty) as Boolean
|
||||||
|
if (increase && attentionLevel < MAX_LEVEL) {
|
||||||
|
difficultyWasIncreased = true
|
||||||
|
attentionLevel++
|
||||||
|
currentBlockWasIncreased = true
|
||||||
|
furhat.say("Dobre, zvýšime náročnosť.")
|
||||||
|
} else {
|
||||||
|
currentBlockWasIncreased = false
|
||||||
|
furhat.say("Dobre, zostaneme na rovnakej úrovni.")
|
||||||
|
}
|
||||||
|
|
||||||
|
doneAtLevel = 0
|
||||||
|
failedQuestions = 0
|
||||||
|
|
||||||
|
buildNewAttentionRound()
|
||||||
|
goto(AskAttention)
|
||||||
|
}
|
||||||
|
}
|
||||||
119
src/main/kotlin/furhatos/app/blank/flow/main/greeting.kt
Normal file
119
src/main/kotlin/furhatos/app/blank/flow/main/greeting.kt
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package furhatos.app.blank.flow.main
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.attention.AttentionTrainingIntro
|
||||||
|
import furhatos.app.blank.flow.main.handlers.Goodbye
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRepeat
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRephrase
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleStop
|
||||||
|
import furhatos.app.blank.flow.main.memory.MemoryTrainingIntro
|
||||||
|
import furhatos.app.blank.flow.main.say_time.TimeTrainingIntro
|
||||||
|
import furhatos.app.blank.flow.main.supporting.CheckCondition
|
||||||
|
import furhatos.app.blank.flow.main.supporting.happyNod
|
||||||
|
import furhatos.app.blank.flow.main.SessionLogger
|
||||||
|
import furhatos.app.blank.flow.main.memory.currentSequence
|
||||||
|
import furhatos.app.blank.flow.main.memory.memoryLevel
|
||||||
|
import furhatos.app.blank.flow.main.supporting.ReadyToTrain
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.TrainingMenuFlags
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.isProxyAvailable
|
||||||
|
import furhatos.app.blank.flow.main.supporting.littleSad
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Repeat
|
||||||
|
import furhatos.app.blank.nlu.base_answer.StopTraining
|
||||||
|
import furhatos.flow.kotlin.State
|
||||||
|
import furhatos.flow.kotlin.onResponse
|
||||||
|
import furhatos.flow.kotlin.state
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.nio.file.StandardOpenOption
|
||||||
|
|
||||||
|
private const val PROXY_BASE_URL = "http://127.0.0.1:8000"
|
||||||
|
|
||||||
|
fun resetRealtimeProxyAsync() {
|
||||||
|
thread(isDaemon = true) {
|
||||||
|
try {
|
||||||
|
val url = URL("$PROXY_BASE_URL/reset")
|
||||||
|
val conn = url.openConnection() as HttpURLConnection
|
||||||
|
conn.requestMethod = "POST"
|
||||||
|
conn.setRequestProperty("Content-Type", "application/json")
|
||||||
|
conn.doOutput = true
|
||||||
|
conn.outputStream.use { it.write("{}".toByteArray()) }
|
||||||
|
conn.inputStream.use { it.readBytes() } // просто чтобы завершить запрос корректно
|
||||||
|
conn.disconnect()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// не ломаем диалог, если прокси недоступен
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val Greeting: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
resetTrainingGameBag() // новый пользователь => новая случайная тройка игр
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = false;
|
||||||
|
|
||||||
|
resetRealtimeProxyAsync() // сброс прокси
|
||||||
|
|
||||||
|
println("user.dir = ${System.getProperty("user.dir")}")
|
||||||
|
println("isLocalProjectRun = ${SessionLogger.isLocalProjectRun()}")
|
||||||
|
println("isRunningOnRobot = ${SessionLogger.isRunningOnRobot()}")
|
||||||
|
|
||||||
|
SessionLogger.clearCsvIfRunningOnRobot()
|
||||||
|
// SessionLogger.flushPendingLogs()
|
||||||
|
SessionLogger.flushPendingLogsAsync()
|
||||||
|
SessionLogger.startNewSession()
|
||||||
|
|
||||||
|
happyNod()
|
||||||
|
furhat.say {
|
||||||
|
random {
|
||||||
|
+"Ahoj! Teším sa na náš rozhovor."
|
||||||
|
+"Dobrý deň! Ďakujem, že ste prišli."
|
||||||
|
+"Dobrý deň, vítam vás."
|
||||||
|
+"Dobrý deň! Som ráda, že vás vidím."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
furhat.say(pendingFileStatusMessage())
|
||||||
|
|
||||||
|
furhat.listen()
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<StopTraining> {
|
||||||
|
if (handleStop(it.intent,
|
||||||
|
"system",
|
||||||
|
"greeting",
|
||||||
|
it.text ?: ""))
|
||||||
|
{
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Repeat>{
|
||||||
|
if (handleRepeat(
|
||||||
|
it.intent,
|
||||||
|
"repeat",
|
||||||
|
"system",
|
||||||
|
"greeting",
|
||||||
|
it.text ?: ""
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
if (handleRephrase(it.intent)){
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
goto(CheckCondition)
|
||||||
|
// goto(TimeTrainingIntro)
|
||||||
|
// goto(MemoryTrainingIntro)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package furhatos.app.blank.flow.main.handlers
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.Idle
|
||||||
|
import furhatos.app.blank.flow.main.supporting.calm
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import furhatos.gestures.Gestures
|
||||||
|
|
||||||
|
val Goodbye: State = state(Parent) {
|
||||||
|
|
||||||
|
onEntry {
|
||||||
|
furhat.gesture(Gestures.BigSmile, async = true)
|
||||||
|
furhat.say {
|
||||||
|
random {
|
||||||
|
+"Ďakujem za dnešok. Prajem vám pekný deň!"
|
||||||
|
+"Bolo mi potešením. Dovidenia!"
|
||||||
|
+"Ďakujem, že ste so mnou cvičili. Majte sa krásne."
|
||||||
|
+"Oddýchnite si a uvidíme sa nabudúce. Dovidenia!"
|
||||||
|
+"Ak budete chcieť pokračovať, som tu pre vás. Dovidenia!"
|
||||||
|
+"Majte sa dobre a dávajte na seba pozor. Dovidenia!"
|
||||||
|
+"Dovidenia!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
furhat.attendNobody()
|
||||||
|
goto(Idle)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
package furhatos.app.blank.flow.main.handlers
|
||||||
|
import furhatos.app.blank.flow.main.SessionLogger
|
||||||
|
import furhatos.app.blank.flow.main.say_time.wrongAttempts
|
||||||
|
import furhatos.app.blank.flow.main.supporting.happyNod
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Repeat
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import furhatos.nlu.IntentInstance
|
||||||
|
|
||||||
|
var lastPhrase: String? = null
|
||||||
|
var lastPhraseIsQuestion: Boolean = false
|
||||||
|
|
||||||
|
fun FlowControlRunner.askRepeatable(text: String) {
|
||||||
|
lastPhrase = text
|
||||||
|
lastPhraseIsQuestion = true
|
||||||
|
furhat.ask(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FlowControlRunner.sayRepeatable(text: String) {
|
||||||
|
lastPhrase = text
|
||||||
|
lastPhraseIsQuestion = false
|
||||||
|
furhat.say(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
//fun FlowControlRunner.handleRepeat(intent: IntentInstance?): Boolean {
|
||||||
|
// if (intent is Repeat) {
|
||||||
|
// val phrase = lastPhrase
|
||||||
|
// if (phrase != null) {
|
||||||
|
// happyNod()
|
||||||
|
// furhat.say("Samozrejme môžem zopakovať.")
|
||||||
|
//
|
||||||
|
// if (lastPhraseIsQuestion) {
|
||||||
|
// furhat.ask(phrase)
|
||||||
|
// } else {
|
||||||
|
// furhat.say(phrase)
|
||||||
|
// furhat.listen()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// furhat.say("Prepáčte, momentálne nemám čo zopakovať.")
|
||||||
|
// furhat.listen()
|
||||||
|
// }
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// return false
|
||||||
|
//}
|
||||||
|
|
||||||
|
fun FlowControlRunner.handleRepeat(
|
||||||
|
intent: IntentInstance?,
|
||||||
|
game: String,
|
||||||
|
question: String,
|
||||||
|
correctAnswer: String = "",
|
||||||
|
userAnswer: String = "",
|
||||||
|
hintUsed: Boolean = false
|
||||||
|
): Boolean {
|
||||||
|
if (intent is Repeat) {
|
||||||
|
val phrase = lastPhrase
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = game,
|
||||||
|
phase = "event",
|
||||||
|
question = question,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "repeat_requested",
|
||||||
|
correctAnswer = correctAnswer,
|
||||||
|
userAnswer = userAnswer,
|
||||||
|
hintUsed = hintUsed
|
||||||
|
)
|
||||||
|
|
||||||
|
if (phrase != null) {
|
||||||
|
happyNod()
|
||||||
|
furhat.say("Samozrejme môžem zopakovať.")
|
||||||
|
|
||||||
|
if (lastPhraseIsQuestion) {
|
||||||
|
furhat.ask(phrase)
|
||||||
|
} else {
|
||||||
|
furhat.say(phrase)
|
||||||
|
furhat.listen()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
furhat.say("Prepáčte, momentálne nemám čo zopakovať.")
|
||||||
|
furhat.listen()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package furhatos.app.blank.flow.main.handlers
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.callProxyRespond
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.isProxyAvailable
|
||||||
|
import furhatos.app.blank.flow.main.supporting.veryHappy
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Rephrase
|
||||||
|
import furhatos.flow.kotlin.FlowControlRunner
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.nlu.IntentInstance
|
||||||
|
|
||||||
|
fun FlowControlRunner.handleRephrase(intent: IntentInstance?): Boolean {
|
||||||
|
if (intent is Rephrase) {
|
||||||
|
val phrase = lastPhrase
|
||||||
|
|
||||||
|
if (phrase != null) {
|
||||||
|
if (isProxyAvailable()) {
|
||||||
|
val rephrased = callProxyRespond(
|
||||||
|
userText = phrase,
|
||||||
|
task = "rephrase",
|
||||||
|
context = mapOf(
|
||||||
|
"original_question" to phrase
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!rephrased.isNullOrBlank()) {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Poviem to pre Vas inak.")
|
||||||
|
askRepeatable(rephrased)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
furhat.say("Poviem to pre Vas inak.")
|
||||||
|
askRepeatable(phrase)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
furhat.say("Prepáčte, momentálne nemám čo preformulovať.")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
package furhatos.app.blank.flow.main.handlers
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.main.SessionLogger
|
||||||
|
import furhatos.app.blank.flow.main.say_time.hintUsedForCurrentQuestion
|
||||||
|
import furhatos.app.blank.flow.main.say_time.questionsSinceLastCheck
|
||||||
|
import furhatos.app.blank.flow.main.say_time.wrongAttempts
|
||||||
|
import furhatos.app.blank.flow.main.supporting.empathy
|
||||||
|
import furhatos.app.blank.nlu.base_answer.StopTraining
|
||||||
|
import furhatos.flow.kotlin.FlowControlRunner
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.nlu.IntentInstance
|
||||||
|
|
||||||
|
//fun FlowControlRunner.handleStop(intent: IntentInstance?): Boolean {
|
||||||
|
// if (intent is StopTraining) {
|
||||||
|
// empathy()
|
||||||
|
// furhat.say{
|
||||||
|
// random{
|
||||||
|
// +"Rozumiem. Zastavíme to, nič sa nedeje."
|
||||||
|
// +"Dobre, zastavme sa tu."
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// wrongAttempts = 0
|
||||||
|
// questionsSinceLastCheck = 0
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// return false
|
||||||
|
//}
|
||||||
|
|
||||||
|
fun FlowControlRunner.handleStop(
|
||||||
|
intent: IntentInstance?,
|
||||||
|
game: String,
|
||||||
|
question: String,
|
||||||
|
userAnswer: String = ""
|
||||||
|
): Boolean {
|
||||||
|
if (intent is StopTraining) {
|
||||||
|
println("DEBUG handleStop: StopTraining matched")
|
||||||
|
SessionLogger.log(
|
||||||
|
game = game,
|
||||||
|
phase = "event",
|
||||||
|
question = question,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "stop_requested",
|
||||||
|
correctAnswer = "",
|
||||||
|
userAnswer = userAnswer,
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
empathy()
|
||||||
|
furhat.say {
|
||||||
|
random {
|
||||||
|
+"Rozumiem. Zastavíme to, nič sa nedeje."
|
||||||
|
+"Dobre, zastavme sa tu."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrongAttempts = 0
|
||||||
|
questionsSinceLastCheck = 0
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
27
src/main/kotlin/furhatos/app/blank/flow/main/idle.kt
Normal file
27
src/main/kotlin/furhatos/app/blank/flow/main/idle.kt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package furhatos.app.blank.flow.main
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.Test
|
||||||
|
import furhatos.flow.kotlin.State
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.flow.kotlin.onUserEnter
|
||||||
|
import furhatos.flow.kotlin.onUserLeave
|
||||||
|
import furhatos.flow.kotlin.state
|
||||||
|
|
||||||
|
val Idle: State = state {
|
||||||
|
onEntry {
|
||||||
|
furhat.attendNobody()
|
||||||
|
}
|
||||||
|
|
||||||
|
onUserEnter {
|
||||||
|
furhat.attend(it)
|
||||||
|
goto(Greeting)
|
||||||
|
// goto(Test)
|
||||||
|
}
|
||||||
|
|
||||||
|
onUserLeave(){
|
||||||
|
// furhat.attend(it)
|
||||||
|
// sayGoodbye()
|
||||||
|
furhat.attendNobody()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,642 @@
|
|||||||
|
package furhatos.app.blank.flow.main.memory
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.SessionLogger
|
||||||
|
import furhatos.app.blank.flow.main.StartQuestion
|
||||||
|
import furhatos.app.blank.flow.main.handlers.Goodbye
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRepeat
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleStop
|
||||||
|
import furhatos.app.blank.flow.main.supporting.AskToContinue
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.WordBank
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.WordEntry
|
||||||
|
import furhatos.app.blank.flow.main.supporting.empathy
|
||||||
|
import furhatos.app.blank.flow.main.supporting.sayPhraseForWrongAnswer
|
||||||
|
import furhatos.app.blank.flow.main.supporting.littleSad
|
||||||
|
import furhatos.app.blank.flow.main.supporting.veryHappy
|
||||||
|
import furhatos.app.blank.nlu.base_answer.DontKnow
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Help
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Repeat
|
||||||
|
import furhatos.app.blank.nlu.base_answer.StopTraining
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.random.Random
|
||||||
|
import furhatos.app.blank.flow.main.say_time.wrongAttempts
|
||||||
|
import furhatos.app.blank.flow.main.say_time.hintUsedForCurrentQuestion
|
||||||
|
import furhatos.app.blank.flow.main.supporting.AskDecreaseDifficulty
|
||||||
|
import furhatos.app.blank.flow.main.supporting.AskIncreaseDifficulty
|
||||||
|
import furhatos.app.blank.flow.main.supporting.ReadyToTrain
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.SmallTalkContext
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.TrainingMenuFlags
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.callProxyRespond
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.genericSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.isProxyAvailable
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.requestSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.sendLogEvent
|
||||||
|
import furhatos.gestures.Gestures
|
||||||
|
|
||||||
|
// -------------------- parametre --------------------
|
||||||
|
|
||||||
|
private const val MAX_LEVEL = 3
|
||||||
|
private const val QUESTIONS_PER_LEVEL = 2
|
||||||
|
|
||||||
|
private const val SUCCESS_THRESHOLD = 0.90
|
||||||
|
private const val NEAR_SUCCESS_THRESHOLD = 0.60
|
||||||
|
|
||||||
|
private const val MAX_SAME_LEVEL_QUESTIONS = 6
|
||||||
|
|
||||||
|
var memoryLevel: Int = 1
|
||||||
|
var sequencesDoneAtLevel: Int = 0
|
||||||
|
|
||||||
|
var sameLevelQuestionsDone: Int = 0
|
||||||
|
var difficultyWasIncreased: Boolean = false
|
||||||
|
|
||||||
|
// koľko otázok v aktuálnom bloku z 2 bolo neúspešných
|
||||||
|
var failedQuestions: Int = 0
|
||||||
|
|
||||||
|
// Bol aktuálny blok z 2 kôl začatý po zvýšení náročnosti
|
||||||
|
var currentBlockWasIncreased: Boolean = false
|
||||||
|
|
||||||
|
private var sequencePresented: Boolean = false
|
||||||
|
var currentSequence: List<WordEntry> = emptyList()
|
||||||
|
|
||||||
|
// slová používateľa po jednom
|
||||||
|
val collectedTokens: MutableList<String> = mutableListOf()
|
||||||
|
|
||||||
|
// pozície chýb v poslednom pokuse
|
||||||
|
var lastWrongPositions: List<Int> = emptyList()
|
||||||
|
|
||||||
|
var memoryHintReason: String = "general"
|
||||||
|
|
||||||
|
// -------------------- hodnotenie pokusu --------------------
|
||||||
|
private fun requiredCorrectCount(n: Int, threshold: Double): Int =
|
||||||
|
ceil(n * threshold).toInt().coerceAtLeast(1)
|
||||||
|
|
||||||
|
|
||||||
|
private fun evaluateCollected(collected: List<String>, target: List<WordEntry>): Pair<Int, List<Int>> {
|
||||||
|
val n = target.size
|
||||||
|
var correct = 0
|
||||||
|
val wrongPos = mutableListOf<Int>()
|
||||||
|
|
||||||
|
for (i in 0 until n) {
|
||||||
|
val userTok = collected.getOrNull(i)
|
||||||
|
val ok = userTok != null && WordsChecker.matchesWord(userTok, target[i])
|
||||||
|
if (ok) correct++ else wrongPos.add(i + 1)
|
||||||
|
}
|
||||||
|
return Pair(correct, wrongPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- generovanie postupnosti --------------------
|
||||||
|
|
||||||
|
private fun lengthForLevel(level: Int, rng: Random = Random.Default): Int = when (level) {
|
||||||
|
1 -> listOf(3, 3).random(rng)
|
||||||
|
2 -> listOf(5, 6).random(rng)
|
||||||
|
else -> listOf(6, 7).random(rng)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildNewSequence() {
|
||||||
|
val len = lengthForLevel(memoryLevel)
|
||||||
|
currentSequence = WordBank.pickSequence(
|
||||||
|
length = len,
|
||||||
|
maxDifficulty = memoryLevel
|
||||||
|
)
|
||||||
|
sequencePresented = false
|
||||||
|
lastWrongPositions = emptyList()
|
||||||
|
collectedTokens.clear()
|
||||||
|
}
|
||||||
|
//==============================================================
|
||||||
|
// Small Talk
|
||||||
|
//==============================================================
|
||||||
|
fun buildMemorySmallTalkContext(): SmallTalkContext {
|
||||||
|
val theme = currentSequence.firstOrNull()?.theme ?: "memory"
|
||||||
|
|
||||||
|
return SmallTalkContext(
|
||||||
|
exercise = "memory",
|
||||||
|
topic = theme,
|
||||||
|
subtopic = "sequence_recall",
|
||||||
|
targetWord = currentSequence.firstOrNull()?.canonical,
|
||||||
|
responseMode = "open"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun memorySmallTalkSmart(nextState: State): State {
|
||||||
|
val ctx = buildMemorySmallTalkContext()
|
||||||
|
val proxyQuestion = requestSmallTalk(ctx)
|
||||||
|
|
||||||
|
return if (proxyQuestion.isNotBlank()) {
|
||||||
|
genericSmallTalk(
|
||||||
|
context = ctx,
|
||||||
|
nextState = nextState,
|
||||||
|
fallbackQuestion = "Spája sa Vám niektoré z týchto slov s niečím známym?",
|
||||||
|
preparedQuestion = proxyQuestion
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
genericSmallTalk(
|
||||||
|
context = ctx,
|
||||||
|
nextState = nextState,
|
||||||
|
fallbackQuestion = "Spája sa Vám niektoré z týchto slov s niečím známym?"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//==============================================================
|
||||||
|
// Hint
|
||||||
|
//==============================================================
|
||||||
|
|
||||||
|
fun requestMemoryExplainWrong(mistakeType: String): String {
|
||||||
|
val sequenceWords = currentSequence.map { it.canonical }.joinToString(", ")
|
||||||
|
val pos = lastWrongPositions.distinct().sorted().joinToString(", ")
|
||||||
|
|
||||||
|
if (!isProxyAvailable()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return callProxyRespond(
|
||||||
|
userText = "Používateľ urobil chybu v cvičení pamäte.",
|
||||||
|
task = "explain_wrong",
|
||||||
|
context = mapOf(
|
||||||
|
"exercise" to "memory",
|
||||||
|
"mistake_type" to mistakeType, // final_failure / partial_wrong
|
||||||
|
"level" to memoryLevel,
|
||||||
|
"sequence_length" to currentSequence.size,
|
||||||
|
"target_sequence" to sequenceWords,
|
||||||
|
"wrong_positions" to pos
|
||||||
|
)
|
||||||
|
) ?: ""
|
||||||
|
}
|
||||||
|
//===============================================
|
||||||
|
// Proces hry
|
||||||
|
//===============================================
|
||||||
|
// Intro
|
||||||
|
|
||||||
|
val MemoryTrainingIntro: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
wrongAttempts = 0
|
||||||
|
hintUsedForCurrentQuestion = false
|
||||||
|
|
||||||
|
sameLevelQuestionsDone = 0
|
||||||
|
difficultyWasIncreased = false
|
||||||
|
|
||||||
|
failedQuestions = 0
|
||||||
|
currentBlockWasIncreased = false
|
||||||
|
|
||||||
|
memoryLevel = 1
|
||||||
|
sequencesDoneAtLevel = 0
|
||||||
|
|
||||||
|
buildNewSequence()
|
||||||
|
goto(AskSequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- AskSequence - zobrazuje zoznam 1 raz, potom prejde k zberu --------------------
|
||||||
|
|
||||||
|
val AskSequence: State = state(Parent) {
|
||||||
|
|
||||||
|
onEntry {
|
||||||
|
furhat.say("Teraz pomenujem niekoľko pojmov.")
|
||||||
|
|
||||||
|
if (!sequencePresented) {
|
||||||
|
furhat.say("Pozorne počúvajte a zapamätajte si ich.")
|
||||||
|
//delay(1000)
|
||||||
|
furhat.say("Poradie slov je také:")
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
|
currentSequence.forEachIndexed { i, w ->
|
||||||
|
furhat.say(w.canonical)
|
||||||
|
if (i != currentSequence.lastIndex) delay(1700)
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1300)
|
||||||
|
furhat.say("Skúste ich teraz zopakovať v rovnakom poradí.")
|
||||||
|
sequencePresented = true
|
||||||
|
|
||||||
|
memoryLogWithDebug("question_shown / asked"){
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "memory",
|
||||||
|
phase = "question_shown",
|
||||||
|
question = "LEVEL_${memoryLevel}_LEN_${currentSequence.size}",
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "asked",
|
||||||
|
correctAnswer = currentSequence.joinToString(", ") { it.canonical },
|
||||||
|
userAnswer = "",
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
|
||||||
|
collectedTokens.clear()
|
||||||
|
goto(CollectSequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- CollectSequence: pocuvanie slov ----------------
|
||||||
|
|
||||||
|
val CollectSequence: State = state(Parent) {
|
||||||
|
|
||||||
|
onEntry {
|
||||||
|
furhat.param.endSilTimeout = 400
|
||||||
|
furhat.attend(users.current)
|
||||||
|
|
||||||
|
furhat.listen()
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<DontKnow> {
|
||||||
|
memoryLogWithDebug("Don't know"){
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "memory",
|
||||||
|
phase = "event",
|
||||||
|
question = "LEVEL_${memoryLevel}_LEN_${currentSequence.size}",
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "dont_know",
|
||||||
|
correctAnswer = currentSequence.joinToString(", ") { it.canonical },
|
||||||
|
userAnswer = it.text ?: "",
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)}
|
||||||
|
|
||||||
|
empathy()
|
||||||
|
furhat.say("Rozumiem, to je v poriadku.")
|
||||||
|
|
||||||
|
if (!hintUsedForCurrentQuestion) {
|
||||||
|
memoryHintReason = "dont_know"
|
||||||
|
if (wrongAttempts < 2) wrongAttempts = 2
|
||||||
|
goto(MemoryHintOffer)
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// napoveda uz bola
|
||||||
|
wrongAttempts = 3
|
||||||
|
lastWrongPositions = (1..currentSequence.size).toList()
|
||||||
|
goto(AfterSequenceResolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Help> {
|
||||||
|
memoryLogWithDebug("Help"){
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "memory",
|
||||||
|
phase = "event",
|
||||||
|
question = "LEVEL_${memoryLevel}_LEN_${currentSequence.size}",
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "help_requested",
|
||||||
|
correctAnswer = currentSequence.joinToString(", ") { it.canonical },
|
||||||
|
userAnswer = it.text ?: "",
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)}
|
||||||
|
|
||||||
|
if (!hintUsedForCurrentQuestion) {
|
||||||
|
veryHappy()
|
||||||
|
|
||||||
|
memoryHintReason = "help"
|
||||||
|
furhat.say("Samozrejme, pomôžem Vám!")
|
||||||
|
|
||||||
|
if (wrongAttempts < 2) wrongAttempts = 2
|
||||||
|
goto(MemoryHintOffer)
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
empathy()
|
||||||
|
furhat.say("Už som Vám raz zopakoval zoznam.")
|
||||||
|
|
||||||
|
furhat.gesture(Gestures.ExpressSad(strength = 0.35, duration = 0.8), async = true)
|
||||||
|
furhat.say("Je mi ľúto, že Vám nepomohol. Skúste to ešte raz.")
|
||||||
|
collectedTokens.clear()
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<StopTraining> {
|
||||||
|
if (handleStop(it.intent,
|
||||||
|
"memory",
|
||||||
|
"LEVEL_${memoryLevel}_LEN_${currentSequence.size}",
|
||||||
|
it.text ?: ""))
|
||||||
|
{
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// onResponse<Repeat> {
|
||||||
|
// if (handleRepeat(
|
||||||
|
// it.intent,
|
||||||
|
// "memory",
|
||||||
|
// "LEVEL_${memoryLevel}_LEN_${currentSequence.size}",
|
||||||
|
// currentSequence.joinToString(", ") { it.canonical },
|
||||||
|
// it.text ?: "",
|
||||||
|
// true
|
||||||
|
// ))
|
||||||
|
// {
|
||||||
|
// return@onResponse
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
if (it.intent is Repeat) {
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "memory",
|
||||||
|
phase = "event",
|
||||||
|
question = "LEVEL_${memoryLevel}_LEN_${currentSequence.size}",
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "repeat_requested",
|
||||||
|
correctAnswer = currentSequence.joinToString(", ") { it.canonical },
|
||||||
|
userAnswer = it.text ?: "",
|
||||||
|
hintUsed = true
|
||||||
|
)
|
||||||
|
|
||||||
|
hintUsedForCurrentQuestion = true
|
||||||
|
collectedTokens.clear()
|
||||||
|
|
||||||
|
furhat.say("Zopakujem Vám zoznam ešte raz.")
|
||||||
|
delay(1000)
|
||||||
|
furhat.say("Slová sú:")
|
||||||
|
delay(1000)
|
||||||
|
currentSequence.forEachIndexed { i, w ->
|
||||||
|
furhat.say(w.canonical)
|
||||||
|
if (i != currentSequence.lastIndex) delay(1200)
|
||||||
|
}
|
||||||
|
reentry()
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
val text = it.text ?: ""
|
||||||
|
val token = WordsChecker.tokenizeMeaningful(text).firstOrNull()
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
// nic zmyslene nebolo
|
||||||
|
|
||||||
|
memoryLogWithDebug("Zber sekvencii - null"){
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "memory",
|
||||||
|
phase = "event",
|
||||||
|
question = "LEVEL_${memoryLevel}_LEN_${currentSequence.size}",
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "unrecognized_token",
|
||||||
|
correctAnswer = currentSequence.joinToString(", ") { it.canonical },
|
||||||
|
userAnswer = text,
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)}
|
||||||
|
|
||||||
|
littleSad()
|
||||||
|
reentry()
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
collectedTokens.add(token)
|
||||||
|
|
||||||
|
furhat.gesture(Gestures.Nod, async = true)
|
||||||
|
|
||||||
|
if (collectedTokens.size < currentSequence.size) {
|
||||||
|
reentry()
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// pokus je cely -> hodnotenie
|
||||||
|
val n = currentSequence.size
|
||||||
|
val requiredSuccess = requiredCorrectCount(n, SUCCESS_THRESHOLD)
|
||||||
|
val requiredNear = requiredCorrectCount(n, NEAR_SUCCESS_THRESHOLD)
|
||||||
|
|
||||||
|
val (correct, wrongPos) = evaluateCollected(collectedTokens, currentSequence)
|
||||||
|
lastWrongPositions = wrongPos
|
||||||
|
|
||||||
|
val targetSequence = currentSequence.joinToString(", ") { it.canonical }
|
||||||
|
val userSequence = collectedTokens.joinToString(", ")
|
||||||
|
|
||||||
|
val resultForLog = when {
|
||||||
|
correct >= requiredSuccess -> "success"
|
||||||
|
correct >= requiredNear -> "partial_success"
|
||||||
|
else -> "fail"
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryLogWithDebug("Zber sekvencii / answer"){
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "memory",
|
||||||
|
phase = "answer",
|
||||||
|
question = "LEVEL_${memoryLevel}_LEN_${currentSequence.size}",
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = resultForLog,
|
||||||
|
correctAnswer = targetSequence,
|
||||||
|
userAnswer = userSequence,
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)}
|
||||||
|
|
||||||
|
when {
|
||||||
|
// full uspech
|
||||||
|
correct >= requiredSuccess -> {
|
||||||
|
wrongAttempts = 0
|
||||||
|
hintUsedForCurrentQuestion = false
|
||||||
|
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Výborne! To bolo správne.")
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
|
goto(memorySmallTalkSmart(AfterSequenceResolved))
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// >= 60% a < 80%
|
||||||
|
correct >= requiredNear -> {
|
||||||
|
wrongAttempts = 0
|
||||||
|
hintUsedForCurrentQuestion = false
|
||||||
|
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Takmer všetky slová ste pomenovali správne!")
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
|
goto(memorySmallTalkSmart(AfterSequenceResolved))
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
wrongAttempts++
|
||||||
|
when (wrongAttempts) {
|
||||||
|
1 -> {
|
||||||
|
val hint = requestMemoryHint("partial_wrong")
|
||||||
|
|
||||||
|
if (hint.isNotBlank()){
|
||||||
|
furhat.say("Dajte mi chvíľu. Pokúsim sa Vám pomôcť.")
|
||||||
|
furhat.say(hint)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
littleSad()
|
||||||
|
furhat.say("Nie celkom správne. Skúste to ešte raz.")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
collectedTokens.clear()
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
sayPhraseForWrongAnswer()
|
||||||
|
collectedTokens.clear()
|
||||||
|
goto(MemoryHintOffer)
|
||||||
|
}
|
||||||
|
else -> goto(AfterSequenceResolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNoResponse {
|
||||||
|
memoryLogWithDebug("No response"){
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "memory",
|
||||||
|
phase = "answer",
|
||||||
|
question = "LEVEL_${memoryLevel}_LEN_${currentSequence.size}",
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "no_response",
|
||||||
|
correctAnswer = currentSequence.joinToString(", ") { it.canonical },
|
||||||
|
userAnswer = "",
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)}
|
||||||
|
|
||||||
|
if (!hintUsedForCurrentQuestion) {
|
||||||
|
if (wrongAttempts < 2) wrongAttempts = 2
|
||||||
|
goto(MemoryHintOffer)
|
||||||
|
} else {
|
||||||
|
wrongAttempts = 3
|
||||||
|
lastWrongPositions = (1..currentSequence.size).toList()
|
||||||
|
goto(AfterSequenceResolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- prechod medzi urovni --------------------
|
||||||
|
|
||||||
|
val AfterSequenceResolved: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
if (wrongAttempts >= 3) {
|
||||||
|
memoryLogWithDebug("Resolved sequence"){
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "memory",
|
||||||
|
phase = "resolved",
|
||||||
|
question = "LEVEL_${memoryLevel}_LEN_${currentSequence.size}",
|
||||||
|
attempt = wrongAttempts,
|
||||||
|
result = "final_fail",
|
||||||
|
correctAnswer = currentSequence.joinToString(", ") { it.canonical },
|
||||||
|
userAnswer = collectedTokens.joinToString(", "),
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)}
|
||||||
|
|
||||||
|
val pos = lastWrongPositions.distinct().sorted().joinToString(", ")
|
||||||
|
val explanation = requestMemoryExplainWrong("final_failure")
|
||||||
|
|
||||||
|
littleSad()
|
||||||
|
furhat.say("Je mi ľúto, ale nedali ste správnu odpoveď. Tentoraz to nevyšlo, ale to nevadí.")
|
||||||
|
|
||||||
|
if (explanation.isNotBlank()){
|
||||||
|
furhat.say(explanation)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
furhat.say("V poslednej odpovedi boli nesprávne slová na pozíciách: $pos.")
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// zatvorenie otazky
|
||||||
|
val questionFailed = wrongAttempts >= 3
|
||||||
|
wrongAttempts = 0
|
||||||
|
hintUsedForCurrentQuestion = false
|
||||||
|
collectedTokens.clear()
|
||||||
|
lastWrongPositions = emptyList()
|
||||||
|
|
||||||
|
sequencesDoneAtLevel++
|
||||||
|
|
||||||
|
if (questionFailed) {
|
||||||
|
failedQuestions++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!difficultyWasIncreased) {
|
||||||
|
sameLevelQuestionsDone++
|
||||||
|
|
||||||
|
if (sameLevelQuestionsDone >= MAX_SAME_LEVEL_QUESTIONS) {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Výborne, dnes už stačí. Ukončíme tuto hru na pamäť. Ďakujem Vám!")
|
||||||
|
delay(1000)
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequencesDoneAtLevel < QUESTIONS_PER_LEVEL) {
|
||||||
|
buildNewSequence()
|
||||||
|
goto(AskSequence)
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memoryLevel >= MAX_LEVEL) {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Týmto sme ukončili dnešnú hru na pamäť. Ďakujem Vám!")
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
|
TrainingMenuFlags.allMemoryQuestionsCompleted = true
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
val wantsToContinue = call(AskToContinue()) as Boolean
|
||||||
|
if (!wantsToContinue) {
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// vrátiť sa, ak boli obe otázky na novej úrovni neúspešné
|
||||||
|
if (currentBlockWasIncreased && failedQuestions >= QUESTIONS_PER_LEVEL && memoryLevel > 1) {
|
||||||
|
|
||||||
|
val goBack = call(AskDecreaseDifficulty) as Boolean
|
||||||
|
|
||||||
|
if (goBack) {
|
||||||
|
memoryLevel--
|
||||||
|
furhat.say("Dobre, vrátime sa na ľahšiu úroveň.")
|
||||||
|
} else {
|
||||||
|
furhat.say("Dobre, zostaneme na rovnakej úrovni.")
|
||||||
|
}
|
||||||
|
|
||||||
|
sequencesDoneAtLevel = 0
|
||||||
|
failedQuestions = 0
|
||||||
|
currentBlockWasIncreased = false
|
||||||
|
|
||||||
|
buildNewSequence()
|
||||||
|
goto(AskSequence)
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val increase = call(AskIncreaseDifficulty) as Boolean
|
||||||
|
if (increase && memoryLevel < MAX_LEVEL) {
|
||||||
|
difficultyWasIncreased = true
|
||||||
|
memoryLevel++
|
||||||
|
currentBlockWasIncreased = true
|
||||||
|
furhat.say("Dobre, zvýšime náročnosť.")
|
||||||
|
} else {
|
||||||
|
currentBlockWasIncreased = false
|
||||||
|
furhat.say("Dobre, zostaneme na rovnakej úrovni.")
|
||||||
|
}
|
||||||
|
|
||||||
|
sequencesDoneAtLevel = 0
|
||||||
|
failedQuestions = 0
|
||||||
|
buildNewSequence()
|
||||||
|
goto(AskSequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun memoryLogDebug(stage: String) {
|
||||||
|
val now = java.time.LocalTime.now()
|
||||||
|
println("[MEMORY DEBUG $now] $stage")
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun memoryLogWithDebug(
|
||||||
|
stage: String,
|
||||||
|
block: () -> Unit
|
||||||
|
) {
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
memoryLogDebug("START log -> $stage")
|
||||||
|
|
||||||
|
try {
|
||||||
|
block()
|
||||||
|
val elapsed = System.currentTimeMillis() - start
|
||||||
|
memoryLogDebug("END log -> $stage (${elapsed} ms)")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val elapsed = System.currentTimeMillis() - start
|
||||||
|
memoryLogDebug("ERROR log -> $stage (${elapsed} ms): ${e.message}")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package furhatos.app.blank.flow.main.memory
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.say_time.hintUsedForCurrentQuestion
|
||||||
|
import furhatos.app.blank.flow.main.supporting.HintOffer
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.callProxyRespond
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.isProxyAvailable
|
||||||
|
import furhatos.app.blank.flow.main.supporting.veryHappy
|
||||||
|
import furhatos.flow.kotlin.State
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.flow.kotlin.state
|
||||||
|
|
||||||
|
fun requestMemoryHint(mistakeType: String): String {
|
||||||
|
val sequenceWords = currentSequence.map { it.canonical }.joinToString(", ")
|
||||||
|
|
||||||
|
if (!isProxyAvailable()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return callProxyRespond(
|
||||||
|
userText = "Používateľ potrebuje nápovedu v cvičení pamäte.",
|
||||||
|
task = "hint",
|
||||||
|
context = mapOf(
|
||||||
|
"exercise" to "memory",
|
||||||
|
"mistake_type" to mistakeType, // partial_wrong / dont_know / no_response / help
|
||||||
|
"level" to memoryLevel,
|
||||||
|
"sequence_length" to currentSequence.size,
|
||||||
|
"target_sequence" to sequenceWords,
|
||||||
|
"wrong_positions" to lastWrongPositions.joinToString(", ")
|
||||||
|
)
|
||||||
|
) ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val MemoryHintOffer: State by lazy {
|
||||||
|
HintOffer(nextState = MemoryHint, exitState = CollectSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
val MemoryHint: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
hintUsedForCurrentQuestion = true
|
||||||
|
collectedTokens.clear()
|
||||||
|
|
||||||
|
val proxyHint = requestMemoryHint(memoryHintReason)
|
||||||
|
|
||||||
|
if (proxyHint.isNotBlank()){
|
||||||
|
furhat.say(proxyHint)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Dobre! Zopakujem Vám zoznam ešte raz.")
|
||||||
|
|
||||||
|
furhat.say("Slová sú:")
|
||||||
|
|
||||||
|
currentSequence.forEachIndexed { i, w ->
|
||||||
|
furhat.say(w.canonical)
|
||||||
|
if (i != currentSequence.lastIndex) delay(1300)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
furhat.say("Prosím, pokračujte.")
|
||||||
|
goto(CollectSequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package furhatos.app.blank.flow.main.memory
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.WordEntry
|
||||||
|
import java.text.Normalizer
|
||||||
|
|
||||||
|
// -------------------- zbytocne slova --------------------
|
||||||
|
private val STOPWORDS: Set<String> = setOf(
|
||||||
|
"eee", "ehm", "hm", "hmm", "mmm",
|
||||||
|
"no", "tak", "teda", "proste", "akoze", "akože", "vlastne",
|
||||||
|
"prosím", "prosim", "prosímťa", "prosimta",
|
||||||
|
"a", "aj", "že", "ze", "potom", "takže", "takze", "iii", "i"
|
||||||
|
)
|
||||||
|
|
||||||
|
object WordsChecker {
|
||||||
|
// normalizacia
|
||||||
|
private fun stripDiacritics(s: String): String {
|
||||||
|
val norm = Normalizer.normalize(s, Normalizer.Form.NFD)
|
||||||
|
return norm.replace("\\p{Mn}+".toRegex(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizeToken(token: String): String =
|
||||||
|
stripDiacritics(token.lowercase())
|
||||||
|
.replace("[^a-z0-9]".toRegex(), "")
|
||||||
|
|
||||||
|
fun tokenizeMeaningful(text: String): List<String> {
|
||||||
|
val cleaned = text
|
||||||
|
.lowercase()
|
||||||
|
.replace("[,.;:!?()\\[\\]{}\"“”„–—]".toRegex(), " ")
|
||||||
|
.replace("\\s+".toRegex(), " ")
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
if (cleaned.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
return cleaned.split(" ")
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.filter { normalizeToken(it).isNotEmpty() }
|
||||||
|
.filter { normalizeToken(it) !in STOPWORDS }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun matchesWord(userToken: String, target: WordEntry): Boolean {
|
||||||
|
val userNorm = normalizeToken(userToken)
|
||||||
|
if (userNorm.isEmpty()) return false
|
||||||
|
|
||||||
|
val acceptable = (target.variants + target.canonical)
|
||||||
|
.map { normalizeToken(it) }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
return userNorm in acceptable
|
||||||
|
}
|
||||||
|
}
|
||||||
907
src/main/kotlin/furhatos/app/blank/flow/main/say_time/AskTime.kt
Normal file
907
src/main/kotlin/furhatos/app/blank/flow/main/say_time/AskTime.kt
Normal file
@ -0,0 +1,907 @@
|
|||||||
|
package furhatos.app.blank.flow.main.say_time
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.SessionLogger
|
||||||
|
import furhatos.app.blank.flow.main.StartQuestion
|
||||||
|
import furhatos.app.blank.flow.main.handlers.Goodbye
|
||||||
|
import furhatos.app.blank.flow.main.handlers.askRepeatable
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRepeat
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRephrase
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleStop
|
||||||
|
import furhatos.app.blank.nlu.base_answer.StopTraining
|
||||||
|
import furhatos.app.blank.flow.main.memory.currentSequence
|
||||||
|
import furhatos.app.blank.flow.main.memory.memoryLevel
|
||||||
|
import furhatos.app.blank.flow.main.pendingFileStatusMessage
|
||||||
|
import furhatos.app.blank.flow.main.supporting.AskToContinue
|
||||||
|
import furhatos.app.blank.nlu.base_answer.DontKnow
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Rephrase
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Help
|
||||||
|
import furhatos.app.blank.flow.main.supporting.CurrentMonthSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.CurrentTimeSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.DayPeriodSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.ReadyToTrain
|
||||||
|
import furhatos.app.blank.flow.main.supporting.TodayDateSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.TomorrowDateSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.TrainingMenuFlags
|
||||||
|
import furhatos.app.blank.flow.main.supporting.WeekdaySmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.calm
|
||||||
|
import furhatos.app.blank.flow.main.supporting.empathy
|
||||||
|
import furhatos.app.blank.flow.main.supporting.happyShake
|
||||||
|
import furhatos.app.blank.flow.main.supporting.littleSad
|
||||||
|
import furhatos.app.blank.flow.main.supporting.explainWhyWrong
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.SmallTalkContext
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.genericSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.requestSmallTalk
|
||||||
|
import furhatos.app.blank.flow.main.supporting.sayPhraseForWrongAnswer
|
||||||
|
import furhatos.app.blank.flow.main.supporting.veryHappy
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Repeat
|
||||||
|
import furhatos.flow.kotlin.State
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.flow.kotlin.state
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
|
var TimeQuestionTypes: MutableList<TimeQuestionType> = mutableListOf(
|
||||||
|
TimeQuestionType.CURRENT_TIME,
|
||||||
|
TimeQuestionType.TODAY_DATE,
|
||||||
|
TimeQuestionType.TOMORROW_DATE,
|
||||||
|
TimeQuestionType.CURRENT_MONTH,
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY,
|
||||||
|
TimeQuestionType.DAY_PERIOD
|
||||||
|
)
|
||||||
|
|
||||||
|
val priorityTimeQuestionOrder: List<TimeQuestionType> = listOf(
|
||||||
|
TimeQuestionType.CURRENT_MONTH,
|
||||||
|
TimeQuestionType.TODAY_DATE,
|
||||||
|
TimeQuestionType.TOMORROW_DATE
|
||||||
|
)
|
||||||
|
|
||||||
|
var priorityIndex: Int = 0
|
||||||
|
private const val MAX_SMALLTALK_PER_SESSION = 3 // половина из 6
|
||||||
|
var smallTalkUsed = 0
|
||||||
|
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// Small Talk
|
||||||
|
//==============================================================
|
||||||
|
fun buildSmallTalkContextTime(question: TimeQuestionType): SmallTalkContext {
|
||||||
|
return when (question) {
|
||||||
|
TimeQuestionType.CURRENT_TIME -> SmallTalkContext(
|
||||||
|
exercise = "time",
|
||||||
|
topic = "time",
|
||||||
|
subtopic = "current_time",
|
||||||
|
responseMode = "open"
|
||||||
|
)
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_DATE -> SmallTalkContext(
|
||||||
|
exercise = "time",
|
||||||
|
topic = "date",
|
||||||
|
subtopic = "today_date",
|
||||||
|
responseMode = "open"
|
||||||
|
)
|
||||||
|
|
||||||
|
TimeQuestionType.TOMORROW_DATE -> SmallTalkContext(
|
||||||
|
exercise = "time",
|
||||||
|
topic = "date",
|
||||||
|
subtopic = "tomorrow_date",
|
||||||
|
responseMode = "open"
|
||||||
|
)
|
||||||
|
|
||||||
|
TimeQuestionType.CURRENT_MONTH -> SmallTalkContext(
|
||||||
|
exercise = "time",
|
||||||
|
topic = "month",
|
||||||
|
subtopic = "current_month",
|
||||||
|
responseMode = "open"
|
||||||
|
)
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY -> SmallTalkContext(
|
||||||
|
exercise = "time",
|
||||||
|
topic = "weekday",
|
||||||
|
subtopic = DateTimeChecker.CorrectTodayWeekday(),
|
||||||
|
responseMode = "open"
|
||||||
|
)
|
||||||
|
|
||||||
|
TimeQuestionType.DAY_PERIOD -> SmallTalkContext(
|
||||||
|
exercise = "time",
|
||||||
|
topic = "day_period",
|
||||||
|
subtopic = lastDayPeriod,
|
||||||
|
responseMode = "yes_no"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fallbackSmallTalk(question: TimeQuestionType): State {
|
||||||
|
return when (question) {
|
||||||
|
TimeQuestionType.CURRENT_TIME -> CurrentTimeSmallTalk
|
||||||
|
TimeQuestionType.TODAY_DATE -> TodayDateSmallTalk
|
||||||
|
TimeQuestionType.TOMORROW_DATE -> TomorrowDateSmallTalk
|
||||||
|
TimeQuestionType.CURRENT_MONTH -> CurrentMonthSmallTalk
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY -> WeekdaySmallTalk
|
||||||
|
TimeQuestionType.DAY_PERIOD -> DayPeriodSmallTalk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun smallTalkManagerTime(question: TimeQuestionType): State {
|
||||||
|
if (smallTalkUsed >= MAX_SMALLTALK_PER_SESSION) return TimeTrainingSuccess
|
||||||
|
|
||||||
|
// kolko otazok este su v liste
|
||||||
|
val denom = TimeQuestionTypes.size + 1
|
||||||
|
val smallTalkLeft = MAX_SMALLTALK_PER_SESSION - smallTalkUsed
|
||||||
|
|
||||||
|
// výpočty šancov
|
||||||
|
val percent = minOf(0.5, smallTalkLeft.toDouble() / denom.toDouble())
|
||||||
|
val doSmallTalk = Random.nextDouble() < percent
|
||||||
|
|
||||||
|
if (!doSmallTalk) return TimeTrainingSuccess
|
||||||
|
if (TimeQuestionTypes.isEmpty()) return TimeTrainingSuccess
|
||||||
|
|
||||||
|
val ctx = buildSmallTalkContextTime(question)
|
||||||
|
val proxyQuestion = requestSmallTalk(ctx)
|
||||||
|
|
||||||
|
smallTalkUsed++
|
||||||
|
|
||||||
|
return if (proxyQuestion.isNotBlank()) {
|
||||||
|
genericSmallTalk(
|
||||||
|
context = ctx,
|
||||||
|
nextState = TimeTrainingSuccess,
|
||||||
|
preparedQuestion = proxyQuestion
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fallbackSmallTalk(question)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun smallTalkAfterQuestion(question: TimeQuestionType): State {
|
||||||
|
if (smallTalkUsed >= MAX_SMALLTALK_PER_SESSION) return TimeTrainingSuccess
|
||||||
|
|
||||||
|
// kolko otazok este su v liste
|
||||||
|
val denom = TimeQuestionTypes.size + 1
|
||||||
|
|
||||||
|
val smallTalkLeft = MAX_SMALLTALK_PER_SESSION - smallTalkUsed
|
||||||
|
|
||||||
|
// výpočty šancov
|
||||||
|
val percent = minOf(0.5, smallTalkLeft.toDouble() / denom.toDouble())
|
||||||
|
val doSmallTalk = Random.nextDouble() < percent
|
||||||
|
|
||||||
|
if (!doSmallTalk) return TimeTrainingSuccess
|
||||||
|
if (TimeQuestionTypes.isEmpty()) return TimeTrainingSuccess
|
||||||
|
|
||||||
|
smallTalkUsed++
|
||||||
|
return when (question) {
|
||||||
|
TimeQuestionType.CURRENT_TIME -> CurrentTimeSmallTalk
|
||||||
|
TimeQuestionType.TODAY_DATE -> TodayDateSmallTalk
|
||||||
|
TimeQuestionType.TOMORROW_DATE -> TomorrowDateSmallTalk
|
||||||
|
TimeQuestionType.CURRENT_MONTH -> CurrentMonthSmallTalk
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY -> WeekdaySmallTalk
|
||||||
|
TimeQuestionType.DAY_PERIOD -> DayPeriodSmallTalk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
|
||||||
|
// vráti náhodný ešte nepoužitý typ a zároveň ho odstráni zo zoznamu.
|
||||||
|
fun pickRandomTimeQuestionType(): TimeQuestionType {
|
||||||
|
// prioritne otazky -> potom ostatne
|
||||||
|
while (priorityIndex < priorityTimeQuestionOrder.size) {
|
||||||
|
val candidate = priorityTimeQuestionOrder[priorityIndex]
|
||||||
|
priorityIndex++
|
||||||
|
|
||||||
|
if (TimeQuestionTypes.remove(candidate)) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val chosen = TimeQuestionTypes.random()
|
||||||
|
TimeQuestionTypes.remove(chosen)
|
||||||
|
return chosen
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetTimeQuestions() {
|
||||||
|
TimeQuestionTypes = mutableListOf(
|
||||||
|
TimeQuestionType.CURRENT_TIME,
|
||||||
|
TimeQuestionType.TODAY_DATE,
|
||||||
|
TimeQuestionType.TOMORROW_DATE,
|
||||||
|
TimeQuestionType.CURRENT_MONTH,
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY,
|
||||||
|
TimeQuestionType.DAY_PERIOD
|
||||||
|
)
|
||||||
|
priorityIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun correctAnswerFor(question: TimeQuestionType): String = when (question) {
|
||||||
|
TimeQuestionType.CURRENT_TIME -> DateTimeChecker.CorrectCurrentTime()
|
||||||
|
TimeQuestionType.TODAY_DATE -> DateTimeChecker.CorrectTodayDate()
|
||||||
|
TimeQuestionType.TOMORROW_DATE -> DateTimeChecker.CorrectTomorrowDate()
|
||||||
|
TimeQuestionType.CURRENT_MONTH -> DateTimeChecker.CorrectCurrentMonth()
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY -> DateTimeChecker.CorrectTodayWeekday()
|
||||||
|
TimeQuestionType.DAY_PERIOD -> DateTimeChecker.CorrectCurrentDayPeriod()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pre treti nespravny pokus
|
||||||
|
fun FlowControlRunner.finalWrongAnswer() {
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "resolved",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts,
|
||||||
|
result = "final_fail",
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = "",
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
lastWrongAttemptsForQuestion = wrongAttempts
|
||||||
|
wrongAttempts = 0
|
||||||
|
|
||||||
|
when (currentTimeQuestionType) {
|
||||||
|
TimeQuestionType.CURRENT_TIME -> {
|
||||||
|
happyShake()
|
||||||
|
furhat.say(
|
||||||
|
"Dali ste nesprávnu odpoveď, ale nič sa nedeje! " +
|
||||||
|
"Správna odpoveď by bola, že je teraz $correct."
|
||||||
|
)
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_DATE -> {
|
||||||
|
happyShake()
|
||||||
|
furhat.say(
|
||||||
|
"Nevyšlo Vám to, ale vôbec to neprekáža. " +
|
||||||
|
"Správna odpoveď by bola, že dnes je $correct. " +
|
||||||
|
"Teraz si to pamätáte!"
|
||||||
|
)
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TOMORROW_DATE -> {
|
||||||
|
empathy()
|
||||||
|
furhat.say(
|
||||||
|
"Vyzerá to, že je to dnes náročné, ale to je v poriadku " +
|
||||||
|
"Správna odpoveď by bola, že zajtra bude $correct. "
|
||||||
|
)
|
||||||
|
furhat.say("Teraz si to pamätáte!")
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.CURRENT_MONTH -> {
|
||||||
|
empathy()
|
||||||
|
furhat.say(
|
||||||
|
"Nechajte to tak, dnes je to náročné. " +
|
||||||
|
"Správna odpoveď by bola, že je teraz mesiac $correct. "
|
||||||
|
)
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY -> {
|
||||||
|
calm()
|
||||||
|
furhat.say(
|
||||||
|
"Tentoraz to nevyšlo, ale nevadí. " +
|
||||||
|
"Správna odpoveď by bola: $correct."
|
||||||
|
)
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.DAY_PERIOD -> {
|
||||||
|
happyShake()
|
||||||
|
furhat.say(
|
||||||
|
"Dali ste nesprávnu odpoveď, ale nič sa nedeje! " +
|
||||||
|
"Správna odpoveď by bola, že je teraz $correct."
|
||||||
|
)
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
goto(TimeTrainingSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Pomocne premenne
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
var currentTimeQuestionType: TimeQuestionType = TimeQuestionType.CURRENT_TIME
|
||||||
|
var questionsSinceLastCheck = 0
|
||||||
|
var questionText: String = ""
|
||||||
|
|
||||||
|
var wrongAttempts = 0
|
||||||
|
var lastWrongAttemptsForQuestion = 0
|
||||||
|
|
||||||
|
var hintUsedForCurrentQuestion: Boolean = false
|
||||||
|
|
||||||
|
var lastDayPeriod: String = ""
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// Proces hry
|
||||||
|
//==============================================================
|
||||||
|
|
||||||
|
// Intro
|
||||||
|
val TimeTrainingIntro: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
wrongAttempts = 0
|
||||||
|
smallTalkUsed = 0
|
||||||
|
questionsSinceLastCheck = 0
|
||||||
|
resetTimeQuestions() //ранее не было
|
||||||
|
hintUsedForCurrentQuestion = false
|
||||||
|
|
||||||
|
currentTimeQuestionType = pickRandomTimeQuestionType()
|
||||||
|
goto(AskTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AskTime: otazky a reakcia na vysledok
|
||||||
|
val AskTime: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
questionText = when (currentTimeQuestionType) {
|
||||||
|
TimeQuestionType.CURRENT_TIME -> "Môžete mi, prosím, povedať, koľko je teraz hodín?"
|
||||||
|
TimeQuestionType.TODAY_DATE -> "Viete mi povedať, aký je dnes dátum?"
|
||||||
|
TimeQuestionType.TOMORROW_DATE -> "Aký dátum bude zajtra?"
|
||||||
|
TimeQuestionType.CURRENT_MONTH -> "Viete, aký je práve mesiac?"
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY -> "Aký je dnes deň v týždni?"
|
||||||
|
TimeQuestionType.DAY_PERIOD -> "Povedzte, prosím, aká je teraz približne denná doba?"
|
||||||
|
}
|
||||||
|
askRepeatable(questionText)
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "question_shown",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "asked",
|
||||||
|
correctAnswer = correctAnswerFor(currentTimeQuestionType),
|
||||||
|
userAnswer = "",
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<DontKnow> {
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "answer",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "dont_know",
|
||||||
|
correctAnswer = correctAnswerFor(currentTimeQuestionType),
|
||||||
|
userAnswer = it.text ?: "",
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
furhat.say(pendingFileStatusMessage())
|
||||||
|
|
||||||
|
empathy()
|
||||||
|
furhat.say("Rozumiem, to je v poriadku.")
|
||||||
|
|
||||||
|
// este nebola napoveda
|
||||||
|
if (!hintUsedForCurrentQuestion) {
|
||||||
|
if (wrongAttempts < 2) wrongAttempts = 2
|
||||||
|
goto(TimeHintOffer)
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
wrongAttempts = 3
|
||||||
|
finalWrongAnswer()
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Rephrase> {
|
||||||
|
handleRephrase(it.intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Help> {
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "event",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "help_requested",
|
||||||
|
correctAnswer = correctAnswerFor(currentTimeQuestionType),
|
||||||
|
userAnswer = it.text ?: "",
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!hintUsedForCurrentQuestion) {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Samozrejme, pomôžem Vám! Dajte mi chvíľu, prosím")
|
||||||
|
if (wrongAttempts < 2) wrongAttempts = 2
|
||||||
|
goto(TimeHint)
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
empathy()
|
||||||
|
furhat.say("Prepáčte, už som Vám dala nápovedu.")
|
||||||
|
delay(700)
|
||||||
|
|
||||||
|
littleSad()
|
||||||
|
furhat.say("Je mi ľúto, že Vám nepomohla. Skúste odpovedať podľa toho, čo si pamätáte.")
|
||||||
|
|
||||||
|
val questionText = when (currentTimeQuestionType) {
|
||||||
|
TimeQuestionType.CURRENT_TIME ->
|
||||||
|
"Môžete mi ešte raz povedať, koľko je teraz približne hodín?"
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_DATE ->
|
||||||
|
"Skúste mi ešte raz povedať, aký je dnes dátum."
|
||||||
|
|
||||||
|
TimeQuestionType.TOMORROW_DATE ->
|
||||||
|
"Skúste mi ešte raz povedať, aký dátum bude zajtra."
|
||||||
|
|
||||||
|
TimeQuestionType.CURRENT_MONTH ->
|
||||||
|
"Skúste mi, prosím, ešte raz povedať, aký je teraz mesiac."
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY ->
|
||||||
|
"Skúste mi ešte raz povedať, aký je dnes deň v týždni."
|
||||||
|
|
||||||
|
TimeQuestionType.DAY_PERIOD ->
|
||||||
|
"Skúste mi, prosím, ešte raz povedať, či je teraz ráno, deň alebo večer."
|
||||||
|
}
|
||||||
|
|
||||||
|
askRepeatable(questionText)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<StopTraining> {
|
||||||
|
if (handleStop(it.intent,
|
||||||
|
"time",
|
||||||
|
currentTimeQuestionType.name,
|
||||||
|
it.text ?: ""))
|
||||||
|
{
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Repeat>{
|
||||||
|
if (handleRepeat(
|
||||||
|
it.intent,
|
||||||
|
"time",
|
||||||
|
currentTimeQuestionType.name,
|
||||||
|
correctAnswerFor(currentTimeQuestionType),
|
||||||
|
it.text ?: "",
|
||||||
|
hintUsedForCurrentQuestion))
|
||||||
|
{
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
val text = it.text ?: ""
|
||||||
|
|
||||||
|
when (currentTimeQuestionType) {
|
||||||
|
TimeQuestionType.CURRENT_TIME -> {
|
||||||
|
val success = DateTimeChecker.isCorrectCurrentTime(text)
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "answer",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = if (success) "success" else "fail",
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
lastWrongAttemptsForQuestion = wrongAttempts
|
||||||
|
wrongAttempts = 0
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Správne! Povedali ste aktuálny čas. ")
|
||||||
|
goto(smallTalkManagerTime(currentTimeQuestionType))
|
||||||
|
} else {
|
||||||
|
wrongAttempts++
|
||||||
|
when (wrongAttempts) {
|
||||||
|
1 -> {
|
||||||
|
littleSad()
|
||||||
|
furhat.say(
|
||||||
|
"Nezdá sa, že to bol aktuálny čas. " +
|
||||||
|
"Skúste ho povedať napríklad ako „je päť hodín“ alebo „je 5:00“."
|
||||||
|
)
|
||||||
|
delay(700)
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
|
||||||
|
2 -> {
|
||||||
|
// val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
explainWhyWrong(
|
||||||
|
question = questionText,
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
attempt = wrongAttempts
|
||||||
|
)
|
||||||
|
goto(TimeHintOffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
finalWrongAnswer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (DateTimeChecker.isCorrectCurrentTime(text)) {
|
||||||
|
// lastWrongAttemptsForQuestion = wrongAttempts
|
||||||
|
// wrongAttempts = 0
|
||||||
|
// veryHappy()
|
||||||
|
// furhat.say("Správne! Povedali ste aktuálny čas. ")
|
||||||
|
// goto(smallTalkManagerTime(currentTimeQuestionType))
|
||||||
|
// } else {
|
||||||
|
// wrongAttempts++
|
||||||
|
// when (wrongAttempts) {
|
||||||
|
// 1 -> {
|
||||||
|
// littleSad()
|
||||||
|
// furhat.say(
|
||||||
|
// "Nezdá sa, že to bol aktuálny čas. " +
|
||||||
|
// "Skúste ho povedať napríklad ako „je päť hodín“ alebo „je 5:00“."
|
||||||
|
// )
|
||||||
|
// delay(700)
|
||||||
|
// reentry()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 2 -> {
|
||||||
|
//// val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
// explainWhyWrong(
|
||||||
|
// question = questionText,
|
||||||
|
// correctAnswer = correct,
|
||||||
|
// userAnswer = text,
|
||||||
|
// attempt = wrongAttempts
|
||||||
|
// )
|
||||||
|
// goto(TimeHintOffer)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// else -> {
|
||||||
|
// finalWrongAnswer()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_DATE -> {
|
||||||
|
val success = DateTimeChecker.isCorrectTodayDate(text)
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "answer",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = if (success) "success" else "fail",
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
lastWrongAttemptsForQuestion = wrongAttempts
|
||||||
|
wrongAttempts = 0
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Správne, dnes je takýto dátum.")
|
||||||
|
goto(smallTalkManagerTime(currentTimeQuestionType))
|
||||||
|
} else {
|
||||||
|
wrongAttempts++
|
||||||
|
when (wrongAttempts) {
|
||||||
|
1 -> {
|
||||||
|
littleSad()
|
||||||
|
furhat.say(
|
||||||
|
"Zdá sa, že to nie je dnešný dátum. " +
|
||||||
|
"Skúste ho povedať ešte raz, napríklad \"dnes je dvadsiatý decembr\"."
|
||||||
|
)
|
||||||
|
delay(700)
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
explainWhyWrong(
|
||||||
|
question = questionText,
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
attempt = wrongAttempts
|
||||||
|
)
|
||||||
|
goto(TimeHintOffer)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
finalWrongAnswer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TOMORROW_DATE -> {
|
||||||
|
val success = DateTimeChecker.isCorrectTomorrowDate(text)
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "answer",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = if (success) "success" else "fail",
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
lastWrongAttemptsForQuestion = wrongAttempts
|
||||||
|
wrongAttempts = 0
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Správne! To je zajtrajší dátum.")
|
||||||
|
goto(smallTalkManagerTime(currentTimeQuestionType))
|
||||||
|
} else {
|
||||||
|
wrongAttempts++
|
||||||
|
when (wrongAttempts) {
|
||||||
|
1 -> {
|
||||||
|
littleSad()
|
||||||
|
furhat.say(
|
||||||
|
"Nie som si istá, že to je zajtrajší dátum. " +
|
||||||
|
"Skúste ho povedať ešte raz."
|
||||||
|
)
|
||||||
|
delay(700)
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
explainWhyWrong(
|
||||||
|
question = questionText,
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
attempt = wrongAttempts
|
||||||
|
)
|
||||||
|
goto(TimeHintOffer)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
finalWrongAnswer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.CURRENT_MONTH -> {
|
||||||
|
val success = DateTimeChecker.isCorrectCurrentMonth(text)
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "answer",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = if (success) "success" else "fail",
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
lastWrongAttemptsForQuestion = wrongAttempts
|
||||||
|
wrongAttempts = 0
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Správne, je to aktuálny mesiac.")
|
||||||
|
goto(smallTalkManagerTime(currentTimeQuestionType))
|
||||||
|
} else {
|
||||||
|
wrongAttempts++
|
||||||
|
when (wrongAttempts) {
|
||||||
|
1 -> {
|
||||||
|
littleSad()
|
||||||
|
furhat.say(
|
||||||
|
"Nezdá sa, že to bol aktuálny mesiac. " +
|
||||||
|
"Skúste ho povedať ešte raz."
|
||||||
|
)
|
||||||
|
delay(700)
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
explainWhyWrong(
|
||||||
|
question = questionText,
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
attempt = wrongAttempts
|
||||||
|
)
|
||||||
|
goto(TimeHintOffer)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
finalWrongAnswer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY -> {
|
||||||
|
val success = DateTimeChecker.isCorrectTodayWeekday(text)
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "answer",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = if (success) "success" else "fail",
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
lastWrongAttemptsForQuestion = wrongAttempts
|
||||||
|
wrongAttempts = 0
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Správne! Povedali ste dnešný deň v týždni.")
|
||||||
|
goto(smallTalkManagerTime(currentTimeQuestionType))
|
||||||
|
} else {
|
||||||
|
wrongAttempts++
|
||||||
|
when (wrongAttempts) {
|
||||||
|
1 -> {
|
||||||
|
littleSad()
|
||||||
|
furhat.say(
|
||||||
|
"Nezdá sa, že to bol správny deň v týždni. " +
|
||||||
|
"Skúste to povedať inak."
|
||||||
|
)
|
||||||
|
delay(700)
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
explainWhyWrong(
|
||||||
|
question = questionText,
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
attempt = wrongAttempts
|
||||||
|
)
|
||||||
|
goto(TimeHintOffer)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
finalWrongAnswer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.DAY_PERIOD -> {
|
||||||
|
val success = DateTimeChecker.isCorrectCurrentDayPeriod(text)
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "answer",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = if (success) "success" else "fail",
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
lastWrongAttemptsForQuestion = wrongAttempts
|
||||||
|
wrongAttempts = 0
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Správne! Povedali ste dnešný deň v týždni.")
|
||||||
|
goto(smallTalkManagerTime(currentTimeQuestionType))
|
||||||
|
} else {
|
||||||
|
wrongAttempts++
|
||||||
|
|
||||||
|
when (wrongAttempts) {
|
||||||
|
1 -> {
|
||||||
|
littleSad()
|
||||||
|
furhat.say(
|
||||||
|
"Nie som si istá, že to sedí s aktuálnou dennou dobou. " +
|
||||||
|
"Skúste povedať, či je teraz skôr ráno, deň alebo večer."
|
||||||
|
)
|
||||||
|
delay(700)
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
|
||||||
|
2 -> {
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
explainWhyWrong(
|
||||||
|
question = questionText,
|
||||||
|
correctAnswer = correct,
|
||||||
|
userAnswer = text,
|
||||||
|
attempt = wrongAttempts
|
||||||
|
)
|
||||||
|
goto(TimeHintOffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
finalWrongAnswer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNoResponse {
|
||||||
|
SessionLogger.log(
|
||||||
|
game = "time",
|
||||||
|
phase = "answer",
|
||||||
|
question = currentTimeQuestionType.name,
|
||||||
|
attempt = wrongAttempts + 1,
|
||||||
|
result = "no_response",
|
||||||
|
correctAnswer = correctAnswerFor(currentTimeQuestionType),
|
||||||
|
userAnswer = "",
|
||||||
|
hintUsed = hintUsedForCurrentQuestion
|
||||||
|
)
|
||||||
|
|
||||||
|
when {
|
||||||
|
wrongAttempts < 2 -> {
|
||||||
|
wrongAttempts = 2
|
||||||
|
goto(TimeHintOffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// napoveda uz bola -> znova nespravny pokus -> hovori odpoved'
|
||||||
|
else -> {
|
||||||
|
val correct = correctAnswerFor(currentTimeQuestionType)
|
||||||
|
|
||||||
|
lastWrongAttemptsForQuestion = 3
|
||||||
|
wrongAttempts = 0
|
||||||
|
|
||||||
|
empathy()
|
||||||
|
furhat.say(
|
||||||
|
"Zdá sa, že je to teraz pre Vás náročné, ale to je v poriadku. " +
|
||||||
|
"Správna odpoveď na otázku je $correct."
|
||||||
|
)
|
||||||
|
delay(1000)
|
||||||
|
goto(TimeTrainingSuccess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val TimeTrainingSuccess: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
when (lastWrongAttemptsForQuestion) {
|
||||||
|
1 -> {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Vy ste šikovný!")
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
calm()
|
||||||
|
furhat.say("Aj keď Vám to zabralo čas, zvládli ste to dobre.")
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
empathy()
|
||||||
|
furhat.say("Nezvladli ste to, ale to je v pohode. Teraz je to pre Vás náročné, ale nezúfajte, časom si to osvojíte.")
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Zvládli ste to veľmi dobre.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TimeQuestionTypes.isEmpty()) {
|
||||||
|
calm()
|
||||||
|
furhat.say("Týmto sme ukončili hru s časom.")
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Ďakujem, bolo to výborné.")
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
|
TrainingMenuFlags.allTimeQuestionsCompleted = true
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
}
|
||||||
|
|
||||||
|
questionsSinceLastCheck++
|
||||||
|
|
||||||
|
val continueOffer = questionsSinceLastCheck >= 2
|
||||||
|
|
||||||
|
if (continueOffer) {
|
||||||
|
val wantsToContinue = call(AskToContinue()) as Boolean
|
||||||
|
if (!wantsToContinue) {
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onEntry
|
||||||
|
}
|
||||||
|
questionsSinceLastCheck = 0
|
||||||
|
}
|
||||||
|
wrongAttempts = 0
|
||||||
|
hintUsedForCurrentQuestion = false
|
||||||
|
|
||||||
|
currentTimeQuestionType = pickRandomTimeQuestionType()
|
||||||
|
goto(AskTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
158
src/main/kotlin/furhatos/app/blank/flow/main/say_time/STT.kt
Normal file
158
src/main/kotlin/furhatos/app/blank/flow/main/say_time/STT.kt
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.StartQuestion
|
||||||
|
import furhatos.app.blank.flow.main.handlers.Goodbye
|
||||||
|
import furhatos.app.blank.flow.main.handlers.askRepeatable
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRepeat
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRephrase
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleStop
|
||||||
|
import furhatos.app.blank.flow.main.say_time.DateTimeChecker
|
||||||
|
import furhatos.app.blank.flow.main.say_time.DayPeriod
|
||||||
|
import furhatos.app.blank.flow.main.say_time.TimeTrainingSuccess
|
||||||
|
import furhatos.app.blank.flow.main.say_time.currentTimeQuestionType
|
||||||
|
import furhatos.app.blank.flow.main.say_time.lastDayPeriod
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.TrainingMenuFlags
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Ano
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Nie
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Rephrase
|
||||||
|
import furhatos.app.blank.nlu.base_answer.StopTraining
|
||||||
|
|
||||||
|
val DayPeriodSmallTalk: State = state(Parent) {
|
||||||
|
|
||||||
|
onEntry {
|
||||||
|
val question4 = when (lastDayPeriod.lowercase()) {
|
||||||
|
"ráno" -> "Povedali ste, že je teraz ráno. Na dobré ráno patria chutné raňajky – už ste raňajkovali?"
|
||||||
|
"deň" -> "Povedali ste, že je teraz deň. Počas dňa sa zíde mať plán – máte dnes niečo naplánované?"
|
||||||
|
else-> "Povedali ste, že je teraz večer. Večer je dobrý na oddych – už ste si dnes trochu oddýchli?"
|
||||||
|
}
|
||||||
|
|
||||||
|
askRepeatable(question4)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Ano> {
|
||||||
|
furhat.say("To je výborne. Pokračujme ďalej.")
|
||||||
|
goto(TimeTrainingSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Nie> {
|
||||||
|
furhat.say("To nevadí. Dôležité je, že sa snažíte. Pokračujme ďalej.")
|
||||||
|
goto(TimeTrainingSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Rephrase> {
|
||||||
|
handleRephrase(it.intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<StopTraining> {
|
||||||
|
if (handleStop(it.intent,
|
||||||
|
"time",
|
||||||
|
currentTimeQuestionType.name,
|
||||||
|
it.text ?: ""))
|
||||||
|
{
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
if (handleStop(it.intent, "time", "small_talk", it.text ?: "")){
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
// if (handleRepeat(it.intent)){
|
||||||
|
// return@onResponse
|
||||||
|
// }
|
||||||
|
|
||||||
|
furhat.ask("Prepáčte, stačí povedať áno alebo nie.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- CURRENT TIME --------------------
|
||||||
|
|
||||||
|
val CurrentTimeSmallTalk: State = SmallTalk {
|
||||||
|
val period = DateTimeChecker.currentDayPeriod()
|
||||||
|
val questionHodiny = when (period) {
|
||||||
|
DayPeriod.MORNING -> "Ešte je ráno, máte celý deň pred sebou. Máte dnes ešte niečo, čo chcete stihnúť?"
|
||||||
|
DayPeriod.DAY -> "Je ešte deň. Máte dnes ešte niečo, čo chcete stihnúť?"
|
||||||
|
DayPeriod.EVENING -> "Už je večer. Máte ešte dnes niečo, čo by ste chceli stihnúť?"
|
||||||
|
}
|
||||||
|
|
||||||
|
listOf(
|
||||||
|
"Ste skôr ranný typ, alebo nočná sova?",
|
||||||
|
questionHodiny
|
||||||
|
).random()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- TODAY DATE --------------------
|
||||||
|
|
||||||
|
val TodayDateSmallTalk: State = SmallTalk {
|
||||||
|
listOf(
|
||||||
|
"Máte dnes nejakú drobnosť, na ktorú sa tešíte?",
|
||||||
|
"Viete si spomenúť, či je dnes niečí sviatok alebo meniny vo vašom okolí?",
|
||||||
|
"Máte radšej začiatok mesiaca, alebo jeho koniec?"
|
||||||
|
).random()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- TOMORROW DATE --------------------
|
||||||
|
|
||||||
|
val TomorrowDateSmallTalk: State = SmallTalk {
|
||||||
|
listOf(
|
||||||
|
"Máte zajtra niečo naplánované?",
|
||||||
|
"Tešíte sa viac na zajtrajšok, alebo ste spokojní s dneškom?",
|
||||||
|
"Zajtra je nový deň — chcete si zajtra niečo dopriať alebo urobiť inak?",
|
||||||
|
"Čo by vám zajtra urobilo radosť, aj keby to bola maličkosť?"
|
||||||
|
).random()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------- CURRENT MONTH --------------------
|
||||||
|
|
||||||
|
val CurrentMonthSmallTalk: State = SmallTalk {
|
||||||
|
listOf(
|
||||||
|
"Čím si ho najviac spájate — počasím, sviatkami, alebo niečím iným?",
|
||||||
|
"Máte v tomto mesiaci niečo, na čo sa radi pripravujete?",
|
||||||
|
"Viete, čo máte na tomto mesiaci najradšej?",
|
||||||
|
"Je tento mesiac pre vás skôr pokojný, alebo máte veľa povinností?"
|
||||||
|
).random()
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------
|
||||||
|
private fun weekdayInPhrase(day: String): String = when (day.lowercase()) {
|
||||||
|
"pondelok" -> "v pondelok"
|
||||||
|
"utorok" -> "v utorok"
|
||||||
|
"streda" -> "v stredu"
|
||||||
|
"štvrtok" -> "vo štvrtok"
|
||||||
|
"piatok" -> "v piatok"
|
||||||
|
"sobota" -> "v sobotu"
|
||||||
|
"nedeľa", "nedela" -> "v nedeľu"
|
||||||
|
else -> "dnes"
|
||||||
|
}
|
||||||
|
|
||||||
|
val WeekdaySmallTalk: State = SmallTalk {
|
||||||
|
val day = DateTimeChecker.CorrectTodayWeekday()
|
||||||
|
val dayLc = day.lowercase()
|
||||||
|
val dayPrep = weekdayInPhrase(day)
|
||||||
|
|
||||||
|
val daySpecific = when (dayLc) {
|
||||||
|
"pondelok" -> "Povedali ste, že je dnes pondelok. Pondelok je často štart týždňa – máte chuť začať deň pomalšie, alebo hneď naplno?"
|
||||||
|
"utorok" -> "Povedali ste, že je dnes utorok. Utorok býva taký pracovný rozbeh – máte dnes niečo, čo chcete vybaviť?"
|
||||||
|
"streda" -> "Povedali ste, že je dnes streda. Streda je polovica týždňa – máte pocit, že týždeň ide rýchlo, alebo pomaly?"
|
||||||
|
"štvrtok" -> "Povedali ste, že je dnes štvrtok. Už sa blíži víkend – tešíte sa na niečo v najbližších dňoch?"
|
||||||
|
"piatok" -> "Povedali ste, že je dnes piatok. Piatok znie príjemne – plánujete si dnes dopriať trochu oddychu?"
|
||||||
|
"sobota" -> "Povedali ste, že je dnes sobota. Sobota je často na oddych – máte dnes niečo príjemné v pláne?"
|
||||||
|
"nedeľa", "nedela" -> "Povedali ste, že je dnes nedeľa. Nedeľa býva pokojná – ako najradšej trávite nedeľu?"
|
||||||
|
else -> "Ďakujem. A aký máte dnes deň?"
|
||||||
|
}
|
||||||
|
|
||||||
|
val universal = listOf(
|
||||||
|
"Je pre vás $day skôr \"pracovný\" alebo \"oddychový\"?",
|
||||||
|
"Máte $dayPrep nejaký malý zvyk alebo rutinu?",
|
||||||
|
"Chcete si dnes radšej naplánovať niečo, alebo nechať deň plynúť voľne?",
|
||||||
|
"Keby ste si mali vybrať jednu vec na dnes, čo by to bolo?"
|
||||||
|
)
|
||||||
|
|
||||||
|
listOf(daySpecific, universal.random()).random()
|
||||||
|
}
|
||||||
@ -0,0 +1,358 @@
|
|||||||
|
package furhatos.app.blank.flow.main.say_time
|
||||||
|
|
||||||
|
import java.time.*
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
enum class TimeQuestionType {
|
||||||
|
CURRENT_TIME,
|
||||||
|
TODAY_DATE,
|
||||||
|
TOMORROW_DATE,
|
||||||
|
CURRENT_MONTH,
|
||||||
|
TODAY_WEEKDAY,
|
||||||
|
DAY_PERIOD
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DayPeriod {
|
||||||
|
MORNING,
|
||||||
|
DAY,
|
||||||
|
EVENING
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
object DateTimeChecker {
|
||||||
|
|
||||||
|
private fun now(): LocalDateTime = LocalDateTime.now()
|
||||||
|
|
||||||
|
// ---------- 'slovo -> cislo' pre mesiac a hodiny ----------
|
||||||
|
|
||||||
|
private val NUMBER_WORDS = mapOf(
|
||||||
|
"nula" to 0, "nultý" to 0,
|
||||||
|
"jeden" to 1, "jedna" to 1, "jedno" to 1, "prvý" to 1, "prvá" to 1,
|
||||||
|
"dva" to 2, "dve" to 2, "druhý" to 2, "druhá" to 2,
|
||||||
|
"tri" to 3, "tretí" to 3, "tretia" to 3,
|
||||||
|
"štyri" to 4, "styri" to 4, "štvrtý" to 4, "štvrtá" to 4,
|
||||||
|
"päť" to 5, "pat" to 5, "piaty" to 5, "piata" to 5,
|
||||||
|
"šesť" to 6, "sest" to 6, "šiesty" to 6, "šiesta" to 6,
|
||||||
|
"sedem" to 7, "siedmy" to 7, "siedma" to 7,
|
||||||
|
"osem" to 8, "ôsmy" to 8, "ôsma" to 8,
|
||||||
|
"deväť" to 9, "devat" to 9, "deviaty" to 9, "deviatá" to 9,
|
||||||
|
|
||||||
|
"desať" to 10, "desat" to 10, "desiaty" to 10, "desiatá" to 10,
|
||||||
|
"jedenásť" to 11, "jedenast" to 11, "jedenásty" to 11, "jedenásta" to 11,
|
||||||
|
"dvanásť" to 12, "dvanast" to 12, "dvanásty" to 12, "dvanásta" to 12,
|
||||||
|
"trinásť" to 13, "trinast" to 13, "trinásty" to 13, "trinásta" to 13,
|
||||||
|
"štrnásť" to 14, "strnast" to 14, "štrnact" to 14, "štrnásty" to 14, "štrnásta" to 14,
|
||||||
|
"pätnásť" to 15, "patnast" to 15, "pätnásty" to 15, "pätnásta" to 15,
|
||||||
|
"šestnásť" to 16, "sestnast" to 16, "šestnásty" to 16, "šestnásta" to 16,
|
||||||
|
"sedemnásť" to 17, "sedemnast" to 17, "sedemnásty" to 17, "sedemnástá" to 17,
|
||||||
|
"osemnásť" to 18, "osemnast" to 18, "osemnásty" to 18, "osemnásta" to 18,
|
||||||
|
"devätnásť" to 19, "devatnast" to 19, "devätnásty" to 19, "devätnásta" to 19,
|
||||||
|
|
||||||
|
"dvadsať" to 20, "dvadsat" to 20, "dvadsiatá" to 20,
|
||||||
|
"dvadsaťjeden" to 21, "dvadsať jedna" to 21, "dvadsiaty prvý" to 21, "dvadsiať prvá" to 21,
|
||||||
|
"dvadsaťdva" to 22, "dvadsať dva" to 22, "dvadsiaty druhý" to 22, "dvadsiať druhá" to 22,
|
||||||
|
"dvadsaťtri" to 23, "dvadsať tri" to 23, "dvadsiaty tretí" to 23, "dvadsiať treťa" to 23
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun numberFromWord(raw: String): Int? {
|
||||||
|
val cleaned = raw
|
||||||
|
.trim('.', ',', ';')
|
||||||
|
.lowercase()
|
||||||
|
return NUMBER_WORDS[cleaned]
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- HODINY ----------------------------------
|
||||||
|
fun parseClockTime(text: String): LocalTime? {
|
||||||
|
val regex = Regex("""\b(\d{1,2})[ ](\d{1,2})\b""")
|
||||||
|
val match = regex.find(text) ?: return null
|
||||||
|
|
||||||
|
val (hStr, mStr) = match.destructured
|
||||||
|
val hour = hStr.toIntOrNull() ?: return null
|
||||||
|
val minute = mStr.toIntOrNull() ?: return null
|
||||||
|
|
||||||
|
if (hour !in 0..23 || minute !in 0..59) return null
|
||||||
|
|
||||||
|
return LocalTime.of(hour, minute)
|
||||||
|
}
|
||||||
|
// -------------- HODINY (len cas ) ----------------------------------
|
||||||
|
private fun parseHour(text: String): Int? {
|
||||||
|
|
||||||
|
val digitRegex = Regex("""\b(\d{1,2})\b""")
|
||||||
|
val digitMatch = digitRegex.find(text)
|
||||||
|
val hDigit = digitMatch?.groupValues?.get(1)?.toIntOrNull()
|
||||||
|
if (hDigit != null && hDigit in 0..23) {
|
||||||
|
return hDigit
|
||||||
|
}
|
||||||
|
|
||||||
|
val wordRegex = Regex("""\b([\p{L}]+)\b""")
|
||||||
|
for (m in wordRegex.findAll(text.lowercase())) {
|
||||||
|
val word = m.groupValues[1]
|
||||||
|
val num = numberFromWord(word) ?: continue
|
||||||
|
if (num in 0..23) {
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isCorrectCurrentTime(
|
||||||
|
text: String,
|
||||||
|
toleranceMinutes: Long = 10,
|
||||||
|
hourToleranceHours: Int = 1 // допуск по часам для "примерного" ответа
|
||||||
|
): Boolean {
|
||||||
|
val nowTime = now().toLocalTime()
|
||||||
|
|
||||||
|
// ak presny cas
|
||||||
|
val detailed = parseClockTime(text)
|
||||||
|
if (detailed != null) {
|
||||||
|
val diffMin = Duration.between(detailed, nowTime).abs().toMinutes()
|
||||||
|
return diffMin <= toleranceMinutes
|
||||||
|
}
|
||||||
|
|
||||||
|
// ak len hodina
|
||||||
|
val hourOnly = parseHour(text) ?: return false
|
||||||
|
val diffHours = kotlin.math.abs(hourOnly - nowTime.hour)
|
||||||
|
return diffHours <= hourToleranceHours
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- DATUM ----------------------------
|
||||||
|
private val MONTH_WORDS = mapOf(
|
||||||
|
"januar" to 1, "január" to 1, "januara" to 1, "januára" to 1,
|
||||||
|
"februar" to 2, "február" to 2, "februara" to 2, "februára" to 2,
|
||||||
|
"marec" to 3, "marca" to 3,
|
||||||
|
"april" to 4, "apríl" to 4, "aprila" to 4, "apríla" to 4,
|
||||||
|
"maj" to 5, "máj" to 5, "maja" to 5, "mája" to 5,
|
||||||
|
"jun" to 6, "jún" to 6, "juna" to 6, "júna" to 6,
|
||||||
|
"jul" to 7, "júl" to 7, "jula" to 7, "júla" to 7,
|
||||||
|
"august" to 8, "augusta" to 8,
|
||||||
|
"september" to 9, "septembra" to 9,
|
||||||
|
"oktober" to 10, "október" to 10, "oktobra" to 10, "októbra" to 10,
|
||||||
|
"november" to 11, "novembra" to 11,
|
||||||
|
"december" to 12, "decembra" to 12
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun monthFromWord(raw: String): Int? {
|
||||||
|
val cleaned = raw
|
||||||
|
.trim('.', ',', ';')
|
||||||
|
.lowercase()
|
||||||
|
return MONTH_WORDS[cleaned]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findAllNumbers(text: String): List<Int> {
|
||||||
|
val regex = Regex("""\b(\d{1,2})\b""")
|
||||||
|
return regex.findAll(text)
|
||||||
|
.mapNotNull { it.groupValues[1].toIntOrNull() }
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseDay(text: String): Int? {
|
||||||
|
val nums = findAllNumbers(text)
|
||||||
|
val digitDay = nums.firstOrNull { it in 1..31 }
|
||||||
|
if (digitDay != null) return digitDay
|
||||||
|
|
||||||
|
// nie su cisla
|
||||||
|
val wordRegex = Regex("""\b([\p{L}]+)\b""")
|
||||||
|
for (m in wordRegex.findAll(text.lowercase())) {
|
||||||
|
val word = m.groupValues[1]
|
||||||
|
val num = numberFromWord(word) ?: continue
|
||||||
|
if (num in 1..31) {
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseMonth(text: String): Int? {
|
||||||
|
val normalized = text.lowercase()
|
||||||
|
|
||||||
|
// standartne nazvy
|
||||||
|
val wordRegex = Regex("""\b([\p{L}]+)\b""")
|
||||||
|
for (m in wordRegex.findAll(normalized)) {
|
||||||
|
val word = m.groupValues[1]
|
||||||
|
val month = monthFromWord(word)
|
||||||
|
if (month != null) return month
|
||||||
|
}
|
||||||
|
|
||||||
|
// cisla
|
||||||
|
val ordinalMonthRegex = Regex("""\b([\p{L}]+)\s+mesiac\w*\b""")
|
||||||
|
val ordMatch = ordinalMonthRegex.find(normalized)
|
||||||
|
if (ordMatch != null) {
|
||||||
|
val ordinalWord = ordMatch.groupValues[1]
|
||||||
|
val num = numberFromWord(ordinalWord)
|
||||||
|
if (num != null && num in 1..12) return num
|
||||||
|
}
|
||||||
|
|
||||||
|
val nums = findAllNumbers(text)
|
||||||
|
if (nums.size >= 2) {
|
||||||
|
val month = nums[1]
|
||||||
|
if (month in 1..12) return month
|
||||||
|
}
|
||||||
|
val single = nums.firstOrNull { it in 1..12 }
|
||||||
|
return single
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dnesny datum --------------------
|
||||||
|
fun isCorrectTodayDate(text: String): Boolean {
|
||||||
|
val today = LocalDate.now()
|
||||||
|
val day = parseDay(text) ?: return false
|
||||||
|
val month = parseMonth(text)
|
||||||
|
|
||||||
|
return if (month == null) {
|
||||||
|
// пользователь сказал только число
|
||||||
|
day == today.dayOfMonth
|
||||||
|
} else {
|
||||||
|
// пользователь сказал и число, и месяц
|
||||||
|
day == today.dayOfMonth && month == today.monthValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zajtra ---------------------------------------------------
|
||||||
|
fun isCorrectTomorrowDate(text: String): Boolean {
|
||||||
|
val tomorrow = LocalDate.now().plusDays(1)
|
||||||
|
val day = parseDay(text) ?: return false
|
||||||
|
val month = parseMonth(text)
|
||||||
|
|
||||||
|
return if (month == null) {
|
||||||
|
day == tomorrow.dayOfMonth
|
||||||
|
} else {
|
||||||
|
day == tomorrow.dayOfMonth && month == tomorrow.monthValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mesiac -----------------------------------------
|
||||||
|
fun isCorrectCurrentMonth(text: String): Boolean {
|
||||||
|
val today = LocalDate.now()
|
||||||
|
val month = parseMonth(text) ?: return false
|
||||||
|
return month == today.monthValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- DEN TYZDN'A ----------
|
||||||
|
|
||||||
|
private val WEEKDAY_WORDS = mapOf(
|
||||||
|
"pondelok" to DayOfWeek.MONDAY, "pondelka" to DayOfWeek.MONDAY,
|
||||||
|
"utorok" to DayOfWeek.TUESDAY, "utorka" to DayOfWeek.TUESDAY,
|
||||||
|
"streda" to DayOfWeek.WEDNESDAY, "stredu" to DayOfWeek.WEDNESDAY,
|
||||||
|
"štvrtok" to DayOfWeek.THURSDAY, "stvrtok" to DayOfWeek.THURSDAY,
|
||||||
|
"štvrtka" to DayOfWeek.THURSDAY, "stvrtka" to DayOfWeek.THURSDAY,
|
||||||
|
"piatok" to DayOfWeek.FRIDAY, "piatka" to DayOfWeek.FRIDAY,
|
||||||
|
"sobota" to DayOfWeek.SATURDAY, "sobotu" to DayOfWeek.SATURDAY,
|
||||||
|
"nedeľa" to DayOfWeek.SUNDAY, "nedela" to DayOfWeek.SUNDAY, "nedeľu" to DayOfWeek.SUNDAY, "nedelu" to DayOfWeek.SUNDAY
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun weekdayFromWord(raw: String): DayOfWeek? {
|
||||||
|
val cleaned = raw
|
||||||
|
.trim('.', ',', ';')
|
||||||
|
.lowercase()
|
||||||
|
return WEEKDAY_WORDS[cleaned]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseWeekday(text: String): DayOfWeek? {
|
||||||
|
val normalized = text.lowercase()
|
||||||
|
val wordRegex = Regex("""\b([\p{L}]+)\b""")
|
||||||
|
for (m in wordRegex.findAll(normalized)) {
|
||||||
|
val word = m.groupValues[1]
|
||||||
|
val weekday = weekdayFromWord(word)
|
||||||
|
if (weekday != null) return weekday
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isCorrectTodayWeekday(text: String): Boolean {
|
||||||
|
val today = LocalDate.now()
|
||||||
|
val weekday = parseWeekday(text) ?: return false
|
||||||
|
|
||||||
|
return weekday == today.dayOfWeek
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- DOBA ----------
|
||||||
|
fun currentDayPeriod(): DayPeriod {
|
||||||
|
val t = now().toLocalTime()
|
||||||
|
return when {
|
||||||
|
t.hour in 5..11 -> DayPeriod.MORNING
|
||||||
|
t.hour in 12..17 -> DayPeriod.DAY
|
||||||
|
else -> DayPeriod.EVENING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDayPeriod(text: String): DayPeriod? {
|
||||||
|
val normalized = text.lowercase()
|
||||||
|
// ráno
|
||||||
|
if (Regex("""\b(ráno|rano|doobeda|dopoludnie)\b""").containsMatchIn(normalized))
|
||||||
|
return DayPeriod.MORNING
|
||||||
|
// deň
|
||||||
|
if (Regex("""\b(deň|den|obed|na obed|poobede|popoludnie)\b""").containsMatchIn(normalized))
|
||||||
|
return DayPeriod.DAY
|
||||||
|
// večer
|
||||||
|
if (Regex("""\b(večer|vecer|podvečer|podvecer)\b""").containsMatchIn(normalized))
|
||||||
|
return DayPeriod.EVENING
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Проверка: правильно ли пользователь назвал текущую "dennú dobu" (ráno / deň / večer). */
|
||||||
|
fun isCorrectCurrentDayPeriod(text: String): Boolean {
|
||||||
|
val userPeriod = parseDayPeriod(text) ?: return false
|
||||||
|
val nowPeriod = currentDayPeriod()
|
||||||
|
return userPeriod == nowPeriod
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- format pre robota ----------
|
||||||
|
|
||||||
|
private val MONTHS_NOMINATIVE = listOf(
|
||||||
|
"január", "február", "marec", "apríl", "máj", "jún",
|
||||||
|
"júl", "august", "september", "október", "november", "december"
|
||||||
|
)
|
||||||
|
|
||||||
|
// private val MONTHS_GENITIVE = listOf(
|
||||||
|
// "januára", "februára", "marca", "apríla", "mája", "júna",
|
||||||
|
// "júla", "augusta", "septembra", "októbra", "novembra", "decembra"
|
||||||
|
// )
|
||||||
|
|
||||||
|
private val WEEKDAYS = mapOf(
|
||||||
|
DayOfWeek.MONDAY to "pondelok",
|
||||||
|
DayOfWeek.TUESDAY to "utorok",
|
||||||
|
DayOfWeek.WEDNESDAY to "streda",
|
||||||
|
DayOfWeek.THURSDAY to "štvrtok",
|
||||||
|
DayOfWeek.FRIDAY to "piatok",
|
||||||
|
DayOfWeek.SATURDAY to "sobota",
|
||||||
|
DayOfWeek.SUNDAY to "nedeľa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hodiny
|
||||||
|
fun CorrectCurrentTime(): String {
|
||||||
|
val t = now().toLocalTime()
|
||||||
|
return t.format(DateTimeFormatter.ofPattern("HH:mm"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnes
|
||||||
|
fun CorrectTodayDate(): String = formatDate(LocalDate.now())
|
||||||
|
|
||||||
|
// zajtra
|
||||||
|
fun CorrectTomorrowDate(): String = formatDate(LocalDate.now().plusDays(1))
|
||||||
|
|
||||||
|
// mesiac
|
||||||
|
fun CorrectCurrentMonth(): String {
|
||||||
|
val m = LocalDate.now().monthValue
|
||||||
|
return MONTHS_NOMINATIVE[m - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// dan tyzdna
|
||||||
|
fun CorrectTodayWeekday(): String {
|
||||||
|
val d = LocalDate.now()
|
||||||
|
val wd = WEEKDAYS[d.dayOfWeek] ?: d.dayOfWeek.name.lowercase()
|
||||||
|
return "$wd"
|
||||||
|
}
|
||||||
|
|
||||||
|
// doba
|
||||||
|
fun CorrectCurrentDayPeriod(): String = when (currentDayPeriod()) {
|
||||||
|
DayPeriod.MORNING -> "ráno"
|
||||||
|
DayPeriod.DAY -> "deň"
|
||||||
|
DayPeriod.EVENING -> "večer"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDate(d: LocalDate): String {
|
||||||
|
val monthGen = MONTHS_NOMINATIVE[d.monthValue - 1]
|
||||||
|
return "${d.dayOfMonth}. $monthGen"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,363 @@
|
|||||||
|
package furhatos.app.blank.flow.main.say_time
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.supporting.HintOffer
|
||||||
|
import furhatos.app.blank.flow.main.supporting.calm
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.callProxyRespond
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.isProxyAvailable
|
||||||
|
import furhatos.flow.kotlin.State
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.flow.kotlin.state
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalTime
|
||||||
|
import java.time.DayOfWeek
|
||||||
|
import java.time.YearMonth
|
||||||
|
|
||||||
|
//-------- Hours ------------
|
||||||
|
private fun hourQuarter(minute: Int): String = when (minute) {
|
||||||
|
in 0..14 -> "prvej"
|
||||||
|
in 15..29 -> "druhej"
|
||||||
|
in 30..44 -> "tretej"
|
||||||
|
else -> "štvrtej"
|
||||||
|
}
|
||||||
|
//------- Date ---------
|
||||||
|
|
||||||
|
private data class DateData(
|
||||||
|
val monthName: String,
|
||||||
|
val lastDay: Int, // 28/29/30/31
|
||||||
|
val rangeStart: Int,
|
||||||
|
val rangeEnd: Int,
|
||||||
|
val section: String // opis pra napovedu
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getDateData(date: LocalDate): DateData {
|
||||||
|
val lastDay = YearMonth.of(date.year, date.month).lengthOfMonth()
|
||||||
|
val day = date.dayOfMonth
|
||||||
|
|
||||||
|
val (start, end, label) = when {
|
||||||
|
day <= 9 -> Triple(1, minOf(9, lastDay), "začiatok mesiaca")
|
||||||
|
day <= 19 -> Triple(10, minOf(19, lastDay), "desiate dni mesiaca")
|
||||||
|
day <= 26 -> Triple(20, minOf(26, lastDay), "dvadsiate dni mesiaca")
|
||||||
|
else -> Triple(27, lastDay, "koniec mesiaca")
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateData(
|
||||||
|
monthName = MonthNominative(date.monthValue),
|
||||||
|
lastDay = lastDay,
|
||||||
|
rangeStart = start,
|
||||||
|
rangeEnd = end,
|
||||||
|
section = label
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------
|
||||||
|
|
||||||
|
private fun MonthNominative(m: Int): String = when (m) {
|
||||||
|
1 -> "január"
|
||||||
|
2 -> "február"
|
||||||
|
3 -> "marec"
|
||||||
|
4 -> "apríl"
|
||||||
|
5 -> "máj"
|
||||||
|
6 -> "jún"
|
||||||
|
7 -> "júl"
|
||||||
|
8 -> "august"
|
||||||
|
9 -> "september"
|
||||||
|
10 -> "október"
|
||||||
|
11 -> "november"
|
||||||
|
else -> "december"
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------- den tyzdna ----------
|
||||||
|
private fun weekdays(d: java.time.DayOfWeek): String = when (d) {
|
||||||
|
java.time.DayOfWeek.MONDAY -> "pondelok"
|
||||||
|
java.time.DayOfWeek.TUESDAY -> "utorok"
|
||||||
|
java.time.DayOfWeek.WEDNESDAY -> "streda"
|
||||||
|
java.time.DayOfWeek.THURSDAY -> "štvrtok"
|
||||||
|
java.time.DayOfWeek.FRIDAY -> "piatok"
|
||||||
|
java.time.DayOfWeek.SATURDAY -> "sobota"
|
||||||
|
java.time.DayOfWeek.SUNDAY -> "nedeľa"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun weekdayIndex(d: java.time.DayOfWeek): Int = when (d) {
|
||||||
|
java.time.DayOfWeek.MONDAY -> 1
|
||||||
|
java.time.DayOfWeek.TUESDAY -> 2
|
||||||
|
java.time.DayOfWeek.WEDNESDAY -> 3
|
||||||
|
java.time.DayOfWeek.THURSDAY -> 4
|
||||||
|
java.time.DayOfWeek.FRIDAY -> 5
|
||||||
|
java.time.DayOfWeek.SATURDAY -> 6
|
||||||
|
java.time.DayOfWeek.SUNDAY -> 7
|
||||||
|
}
|
||||||
|
private fun weekdayNumber(n: Int): String = when (n) {
|
||||||
|
1 -> "prvý"
|
||||||
|
2 -> "druhý"
|
||||||
|
3 -> "tretí"
|
||||||
|
4 -> "štvrtý"
|
||||||
|
5 -> "piaty"
|
||||||
|
6 -> "šiesty"
|
||||||
|
else -> "siedmy"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prevDay(d: DayOfWeek): DayOfWeek =
|
||||||
|
if (d == DayOfWeek.MONDAY) DayOfWeek.SUNDAY else DayOfWeek.of(d.value - 1)
|
||||||
|
|
||||||
|
private fun nextDay(d: DayOfWeek): DayOfWeek =
|
||||||
|
if (d == DayOfWeek.SUNDAY) DayOfWeek.MONDAY else DayOfWeek.of(d.value + 1)
|
||||||
|
|
||||||
|
private fun weekSection(idx: Int): String = when (idx) {
|
||||||
|
1, 2 -> "začiatok týždňa"
|
||||||
|
3, 4 -> "stred týždňa"
|
||||||
|
else -> "koniec týždňa"
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------- Mesiac ------------
|
||||||
|
private data class SeasonMonth(
|
||||||
|
val season: String,
|
||||||
|
val n: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun seasonMonth(month: Int): SeasonMonth = when (month) {
|
||||||
|
12 -> SeasonMonth("zimy", 1)
|
||||||
|
1 -> SeasonMonth("zimy", 2)
|
||||||
|
2 -> SeasonMonth("zimy", 3)
|
||||||
|
|
||||||
|
3 -> SeasonMonth("jari", 1)
|
||||||
|
4 -> SeasonMonth("jari", 2)
|
||||||
|
5 -> SeasonMonth("jari", 3)
|
||||||
|
|
||||||
|
6 -> SeasonMonth("leta", 1)
|
||||||
|
7 -> SeasonMonth("leta", 2)
|
||||||
|
8 -> SeasonMonth("leta", 3)
|
||||||
|
|
||||||
|
9 -> SeasonMonth("jesene", 1)
|
||||||
|
10 -> SeasonMonth("jesene", 2)
|
||||||
|
else -> SeasonMonth("jesene", 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun seasonNumber(n: Int): String = when (n) {
|
||||||
|
1 -> "prvý"
|
||||||
|
2 -> "druhý"
|
||||||
|
else -> "tretí"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun season(month: Int): String = when (month) {
|
||||||
|
12, 1, 2 -> "Teraz je zima."
|
||||||
|
3, 4, 5 -> "Teraz je jar."
|
||||||
|
6, 7, 8 -> "Teraz je leto."
|
||||||
|
else -> "Teraz je jeseň."
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildHintContext(type: TimeQuestionType, baseHint: String): Map<String, Any?> {
|
||||||
|
val ctx = mutableMapOf<String, Any?>(
|
||||||
|
"task" to "hint",
|
||||||
|
"language" to "sk",
|
||||||
|
"question_type" to type.name,
|
||||||
|
"base_hint" to baseHint,
|
||||||
|
"rules" to mapOf(
|
||||||
|
"do_not_reveal_exact_answer" to true,
|
||||||
|
"max_sentences" to 2,
|
||||||
|
"keep_game_rules" to true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Если у тебя есть lastQuestionText (мы добавляли для explain_wrong) — раскомментируй:
|
||||||
|
// ctx["question"] = lastQuestionText
|
||||||
|
|
||||||
|
when (type) {
|
||||||
|
TimeQuestionType.CURRENT_TIME -> {
|
||||||
|
val now = LocalTime.now()
|
||||||
|
val h = now.hour
|
||||||
|
val next = (h + 1) % 24
|
||||||
|
ctx["hour_from"] = if (h == 0) "0" else h.toString()
|
||||||
|
ctx["hour_to"] = if (next == 0) "0" else next.toString()
|
||||||
|
ctx["minute_hint"] = hourQuarter(now.minute)
|
||||||
|
ctx["suggested_formats"] = listOf("je päť hodín", "5:00", "okolo piatej")
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_DATE -> {
|
||||||
|
val d = getDateData(LocalDate.now())
|
||||||
|
ctx["expected_format"] = "DD.MM.YYYY"
|
||||||
|
ctx["range_start"] = d.rangeStart
|
||||||
|
ctx["range_end"] = d.rangeEnd
|
||||||
|
ctx["section"] = d.section
|
||||||
|
ctx["month_name"] = d.monthName
|
||||||
|
ctx["year"] = LocalDate.now().year
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TOMORROW_DATE -> {
|
||||||
|
val today = LocalDate.now()
|
||||||
|
val tomorrow = today.plusDays(1)
|
||||||
|
val d = getDateData(tomorrow)
|
||||||
|
val isNewMonth = tomorrow.dayOfMonth == 1 || tomorrow.month != today.month
|
||||||
|
|
||||||
|
ctx["expected_format"] = "DD.MM.YYYY"
|
||||||
|
ctx["rule"] = "Zajtra je o jeden deň neskôr ako dnes."
|
||||||
|
ctx["is_new_month"] = isNewMonth
|
||||||
|
ctx["month_name"] = d.monthName
|
||||||
|
ctx["range_start"] = d.rangeStart
|
||||||
|
ctx["range_end"] = d.rangeEnd
|
||||||
|
ctx["section"] = d.section
|
||||||
|
ctx["year"] = tomorrow.year
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.CURRENT_MONTH -> {
|
||||||
|
val month = LocalDate.now().monthValue
|
||||||
|
val info = seasonMonth(month)
|
||||||
|
ctx["answer_kind"] = "month_name"
|
||||||
|
ctx["season"] = info.season
|
||||||
|
ctx["month_in_season"] = info.n
|
||||||
|
ctx["allowed_hints"] = listOf("season", "month_number_without_name", "first_letter")
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY -> {
|
||||||
|
val today = LocalDate.now()
|
||||||
|
val wd = today.dayOfWeek
|
||||||
|
val i = weekdayIndex(wd)
|
||||||
|
|
||||||
|
ctx["answer_kind"] = "weekday_name"
|
||||||
|
ctx["weekday_index"] = i
|
||||||
|
ctx["week_section"] = weekSection(i)
|
||||||
|
ctx["yesterday"] = weekdays(prevDay(wd))
|
||||||
|
ctx["tomorrow"] = weekdays(nextDay(wd))
|
||||||
|
ctx["allowed_hints"] = listOf("weekday_or_weekend", "order_in_week_without_name")
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.DAY_PERIOD -> {
|
||||||
|
ctx["options"] = listOf("ráno", "deň", "večer")
|
||||||
|
ctx["rough_ranges"] = mapOf(
|
||||||
|
"ráno" to "5–11",
|
||||||
|
"deň" to "12–17",
|
||||||
|
"večer" to "18–23"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------
|
||||||
|
|
||||||
|
val TimeHintOffer: State by lazy {
|
||||||
|
HintOffer(nextState = TimeHint, exitState = AskTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
val TimeHint = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
hintUsedForCurrentQuestion = true
|
||||||
|
val baseHint = when (currentTimeQuestionType) {
|
||||||
|
|
||||||
|
TimeQuestionType.CURRENT_TIME -> {
|
||||||
|
val now = LocalTime.now()
|
||||||
|
val h = now.hour
|
||||||
|
val next = (h + 1) % 24
|
||||||
|
val quarter = hourQuarter(now.minute)
|
||||||
|
|
||||||
|
val hourFrom = if (h == 0) "0" else h.toString()
|
||||||
|
val hourTo = if (next == 0) "0" else next.toString()
|
||||||
|
|
||||||
|
val hints = listOf(
|
||||||
|
"Malá nápoveda: je to medzi $hourFrom a $hourTo hodinou.",
|
||||||
|
"Dobre! Tu je moja nápoveda: teraz je čas niekde medzi $hourFrom a $hourTo hodinou, skôr v $quarter časti tejto hodiny.",
|
||||||
|
"Skúste odhad: je to medzi $hourFrom a $hourTo hodinou. A minúty sú približne v $quarter časti hodiny."
|
||||||
|
)
|
||||||
|
|
||||||
|
calm()
|
||||||
|
hints.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_DATE -> {
|
||||||
|
val d = getDateData(LocalDate.now())
|
||||||
|
|
||||||
|
val hints = listOf(
|
||||||
|
"Pomôcka: dnešné číslo je medzi ${d.rangeStart} a ${d.rangeEnd}",
|
||||||
|
"Teraz je ${d.section}, teda číslo je medzi ${d.rangeStart} a ${d.rangeEnd}."
|
||||||
|
)
|
||||||
|
|
||||||
|
calm()
|
||||||
|
hints.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TOMORROW_DATE -> {
|
||||||
|
val today = LocalDate.now()
|
||||||
|
val tomorrow = today.plusDays(1)
|
||||||
|
val d = getDateData(tomorrow)
|
||||||
|
|
||||||
|
val isNewMonth = tomorrow.dayOfMonth == 1 || tomorrow.month != today.month
|
||||||
|
|
||||||
|
val hints = mutableListOf<String>()
|
||||||
|
|
||||||
|
if (!isNewMonth) {
|
||||||
|
hints += "Malá nápoveda: zajtra bude stále v mesiaci ${d.monthName}."
|
||||||
|
}
|
||||||
|
if (isNewMonth) {
|
||||||
|
hints += "Malá nápoveda: zajtra už bude v mesiaci ${d.monthName}."
|
||||||
|
}
|
||||||
|
|
||||||
|
hints += "Zajtra spadá do časti ${d.section}. Skúste číslo medzi ${d.rangeStart} a ${d.rangeEnd}."
|
||||||
|
hints += "Dobre! Tu je moja nápoveda: číslo zajtrajšieho dňa je medzi ${d.rangeStart} a ${d.rangeEnd}"
|
||||||
|
|
||||||
|
calm()
|
||||||
|
hints.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.CURRENT_MONTH -> {
|
||||||
|
|
||||||
|
|
||||||
|
val month = LocalDate.now().monthValue
|
||||||
|
val info = seasonMonth(month)
|
||||||
|
val number = seasonNumber(info.n)
|
||||||
|
|
||||||
|
val hints = listOf(
|
||||||
|
"Malá nápoveda: teraz je $number mesiac ${info.season}. Pamätáte si, ktorý mesiac to je?",
|
||||||
|
"Dobre! Tu je moja nápoveda: sme v $number mesiaci ${info.season}.",
|
||||||
|
"Fajn! Skúsme to takto: ${season(month)}, čiže ide o $number mesiac ${info.season}. Ktorý mesiac to môže byť?",
|
||||||
|
)
|
||||||
|
|
||||||
|
calm()
|
||||||
|
hints.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.TODAY_WEEKDAY -> {
|
||||||
|
val today = LocalDate.now()
|
||||||
|
val wd = today.dayOfWeek
|
||||||
|
|
||||||
|
val i = weekdayIndex(today.dayOfWeek)
|
||||||
|
val n = weekdayNumber(i)
|
||||||
|
|
||||||
|
val yesterdayWd = weekdays(prevDay(wd))
|
||||||
|
val tomorrowWd = weekdays(nextDay(wd))
|
||||||
|
|
||||||
|
val section = weekSection(i)
|
||||||
|
|
||||||
|
val hints = listOf(
|
||||||
|
"Fajn! Moja malá nápoveda: dnes je $n deň v týždni.",
|
||||||
|
"Tak teda, moja nápoveda: v týždni je to $n deň (počítame od pondelka).",
|
||||||
|
"Včera bol $yesterdayWd a zajtra bude $tomorrowWd. Aký deň je potom dnes?",
|
||||||
|
"Teraz je $section. Skúste si spomenúť, aký to je konkrétny deň."
|
||||||
|
)
|
||||||
|
|
||||||
|
calm()
|
||||||
|
hints.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeQuestionType.DAY_PERIOD -> {
|
||||||
|
calm()
|
||||||
|
"Fajn! Malá pomôcka: ráno je približne päť až jedenásť, deň dvanásť až sedemnásť a večer osemnásť až dvadsaťtri. Čo z toho je teraz najbližšie?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val ctx = buildHintContext(currentTimeQuestionType, baseHint)
|
||||||
|
|
||||||
|
val gptHint = if (isProxyAvailable()) {
|
||||||
|
callProxyRespond(
|
||||||
|
userText = "HINT_REQUEST",
|
||||||
|
task = "hint",
|
||||||
|
context = ctx
|
||||||
|
)
|
||||||
|
} else null
|
||||||
|
|
||||||
|
val finalHint = if (!gptHint.isNullOrBlank()) gptHint else baseHint
|
||||||
|
furhat.say(finalHint)
|
||||||
|
|
||||||
|
goto(AskTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Ano
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Nie
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
|
||||||
|
fun AskToContinue() = state(Parent) {
|
||||||
|
|
||||||
|
onEntry {
|
||||||
|
furhat.ask(
|
||||||
|
"Chcete pokračovať hrať alebo by ste chceli skončiť?"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Ano> {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Výborne, budeme pokračovať.")
|
||||||
|
delay(700)
|
||||||
|
terminate(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Nie> {
|
||||||
|
empathy()
|
||||||
|
furhat.say("Rozumiem, nebudeme pokračovať hráť v tuto hru.")
|
||||||
|
delay(700)
|
||||||
|
terminate(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
furhat.say("Prepáčte, nerozumel som. Odpovedzte prosím chcete pokračovať alebo nechcete.")
|
||||||
|
delay(700)
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.StartQuestion
|
||||||
|
import furhatos.app.blank.flow.main.handlers.Goodbye
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRepeat
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRephrase
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleStop
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.TrainingMenuFlags
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import furhatos.app.blank.nlu.other_responses.FeelGood
|
||||||
|
import furhatos.app.blank.nlu.other_responses.FeelBad
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Ano
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Nie
|
||||||
|
|
||||||
|
|
||||||
|
val CheckCondition: State = state(Parent) {
|
||||||
|
|
||||||
|
onEntry {
|
||||||
|
TrainingMenuFlags.resetAll()
|
||||||
|
|
||||||
|
furhat.ask {
|
||||||
|
random {
|
||||||
|
+"Ako sa máte?"
|
||||||
|
+"Ako sa dnes cítite?"
|
||||||
|
+"Ako sa vám dnes darí?"
|
||||||
|
+"Máte sa dobre?"
|
||||||
|
+"Ako sa cítite práve teraz?"
|
||||||
|
+"Ako sa cítite po dnešku?"
|
||||||
|
+"Je vám dnes dobre?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<FeelGood> {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Som rád, že sa cítite dobre.")
|
||||||
|
delay(1000)
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onResponse<FeelBad> {
|
||||||
|
empathy()
|
||||||
|
furhat.say(
|
||||||
|
"Rozumiem, nechcem vás dnes zaťažovať. "
|
||||||
|
+ "Dnes je lepšie odpočívať."
|
||||||
|
)
|
||||||
|
goto(Goodbye)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
happyNod()
|
||||||
|
furhat.say("Ďakujem za odpoveď. Môžme skusíť si napriek tomu krátku hru.")
|
||||||
|
goto(StartQuestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ReadyToTrain(nextState: State, exitState: State): State = state(Parent) {
|
||||||
|
|
||||||
|
fun prompt(): String {
|
||||||
|
return if (TrainingMenuFlags.allExercisesCompleted())
|
||||||
|
"Dnes sme už už odohrali všetky hry. Chcete si niektoré zopakovať?"
|
||||||
|
else if (TrainingMenuFlags.hasTrainedOnce)
|
||||||
|
"Chcete si zahrať inú hru?"
|
||||||
|
else
|
||||||
|
"Chcete si zahrať hru?"
|
||||||
|
}
|
||||||
|
|
||||||
|
onEntry {
|
||||||
|
furhat.ask(prompt())
|
||||||
|
}
|
||||||
|
|
||||||
|
onReentry {
|
||||||
|
furhat.ask(prompt())
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Ano> {
|
||||||
|
veryHappy()
|
||||||
|
furhat.say("Výborne, tak poďme na to.")
|
||||||
|
delay(1000)
|
||||||
|
goto(nextState)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Nie> {
|
||||||
|
empathy()
|
||||||
|
furhat.say("Rozumiem, môžeme to skúsiť inokedy.")
|
||||||
|
goto(exitState)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
if (handleRephrase(it.intent)){
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
// if (handleRepeat(it.intent)){
|
||||||
|
// return@onResponse
|
||||||
|
// }
|
||||||
|
if (handleStop(it.intent, "system", "check_condition", it.text ?: "")){
|
||||||
|
goto(Goodbye)
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
furhat.say("Prepáčte, nerozumela som.")
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Ano
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Nie
|
||||||
|
import furhatos.flow.kotlin.State
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.flow.kotlin.onResponse
|
||||||
|
import furhatos.flow.kotlin.state
|
||||||
|
|
||||||
|
val AskIncreaseDifficulty: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
furhat.ask("Chcete, aby som povedal viac slov?")
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Ano> { terminate(true) }
|
||||||
|
onResponse<Nie> { terminate(false) }
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
furhat.say("Prepáčte, odpovedzte prosím áno alebo nie.")
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val AskDecreaseDifficulty: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
furhat.ask("Chcete sa vrátiť na predchádzajúcu, ľahšiu úroveň náročnosti?")
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Ano> { terminate(true) }
|
||||||
|
onResponse<Nie> { terminate(false) }
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
furhat.say("Prepáčte, odpovedzte prosím áno alebo nie.")
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.Idle
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRepeat
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleStop
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Ano
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Nie
|
||||||
|
import furhatos.gestures.Gestures
|
||||||
|
|
||||||
|
|
||||||
|
fun HintOffer(nextState: State, exitState: State): State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
calm()
|
||||||
|
furhat.ask {
|
||||||
|
random {
|
||||||
|
+"Chcete malú nápovedu?"
|
||||||
|
+"Pomôže Vám malá nápoveda?"
|
||||||
|
+"Možno by som mala Vám dať nápovedu?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Ano> {
|
||||||
|
calm()
|
||||||
|
goto(nextState)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Nie> {
|
||||||
|
empathy()
|
||||||
|
furhat.say("Dobre! Tak potom jednoducho budeme pokračovať.")
|
||||||
|
goto(exitState)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
furhat.say {
|
||||||
|
random {
|
||||||
|
+"Stačí povedať áno alebo nie."
|
||||||
|
+"Prosím, povedzte áno alebo nie."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting
|
||||||
|
|
||||||
|
import furhatos.flow.kotlin.FlowControlRunner
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.gestures.BasicParams
|
||||||
|
import furhatos.gestures.Gesture
|
||||||
|
import furhatos.gestures.Gestures
|
||||||
|
import furhatos.gestures.defineGesture
|
||||||
|
|
||||||
|
fun FlowControlRunner.happyNod(async: Boolean = true) {
|
||||||
|
furhat.gesture(Gestures.Smile, async = async)
|
||||||
|
furhat.gesture(Gestures.Nod, async = async)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FlowControlRunner.happyShake(async: Boolean = true) {
|
||||||
|
furhat.gesture(Gestures.Shake, async = async)
|
||||||
|
furhat.gesture(Gestures.Smile, async = async)
|
||||||
|
}
|
||||||
|
|
||||||
|
val LittleSadHeadDown: Gesture = defineGesture("LittleSadHeadDown") {
|
||||||
|
frame(0.25) {
|
||||||
|
BasicParams.NECK_TILT to 2.0
|
||||||
|
}
|
||||||
|
frame(0.55) {
|
||||||
|
BasicParams.NECK_TILT to 5.0
|
||||||
|
}
|
||||||
|
frame(0.9, 2.0) {
|
||||||
|
BasicParams.NECK_TILT to 9.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// плавное возвращение
|
||||||
|
frame(2.35) {
|
||||||
|
BasicParams.NECK_TILT to 5.0
|
||||||
|
}
|
||||||
|
frame(2.65) {
|
||||||
|
BasicParams.NECK_TILT to 1.0
|
||||||
|
}
|
||||||
|
reset(3.0)
|
||||||
|
}
|
||||||
|
fun FlowControlRunner.littleSad(async: Boolean = true) {
|
||||||
|
// грустная эмоция
|
||||||
|
furhat.gesture(Gestures.ExpressSad(strength = 0.4, duration = 0.8), async = true)
|
||||||
|
|
||||||
|
// лёгкий наклон головы вниз примерно на 2 секунды
|
||||||
|
furhat.gesture(LittleSadHeadDown, async = async)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FlowControlRunner.empathy(async: Boolean = true) {
|
||||||
|
furhat.gesture(Gestures.CloseEyes, async = false)
|
||||||
|
furhat.gesture(Gestures.Nod, async = async)
|
||||||
|
delay(700)
|
||||||
|
furhat.gesture(Gestures.OpenEyes, async = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FlowControlRunner.calm(async: Boolean = true) {
|
||||||
|
furhat.gesture(Gestures.Smile, async = async)
|
||||||
|
furhat.gesture(Gestures.Blink, async = async)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FlowControlRunner.veryHappy(async: Boolean = true) {
|
||||||
|
furhat.gesture(Gestures.BigSmile, async = async)
|
||||||
|
furhat.gesture(Gestures.BrowRaise, async = async)
|
||||||
|
}
|
||||||
|
|
||||||
|
val SmileIdle: Gesture = defineGesture("SmileIdle") {
|
||||||
|
frame(0.2, persist = true) {
|
||||||
|
BasicParams.SMILE_CLOSED to 0.70
|
||||||
|
BasicParams.BROW_UP_LEFT to 0.05
|
||||||
|
BasicParams.BROW_UP_RIGHT to 0.05
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.StartQuestion
|
||||||
|
import furhatos.app.blank.flow.main.handlers.Goodbye
|
||||||
|
import furhatos.app.blank.flow.main.handlers.askRepeatable
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRepeat
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRephrase
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleStop
|
||||||
|
import furhatos.app.blank.flow.main.say_time.TimeTrainingSuccess
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.TrainingMenuFlags
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Ano
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Rephrase
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
|
||||||
|
fun neutralAck(): String = listOf(
|
||||||
|
"Rozumiem, ďakujem.",
|
||||||
|
"Rozumiem. Vážim si, že ste mi to povedali.",
|
||||||
|
"Ďakujem, že ste mi to povedali.",
|
||||||
|
"Je dobre, že o tom môžeme hovoriť.",
|
||||||
|
"Ďakujem, že ste mi venovali čas a porozprávali mi o tom.",
|
||||||
|
"Dobre, vnímam to. Ďakujem, že ste sa otvorili.",
|
||||||
|
"Ďakujem za vaše slová.",
|
||||||
|
).random()
|
||||||
|
|
||||||
|
fun positiveAck(): String = listOf(
|
||||||
|
"To ma veľmi teší.",
|
||||||
|
"To je príjemné počuť.",
|
||||||
|
"Som rada, že to takto vnímate.",
|
||||||
|
"To znie naozaj dobre.",
|
||||||
|
"Teší ma, že sa vám darí.",
|
||||||
|
"To je skvelé, ďakujem, že ste sa podelili.",
|
||||||
|
).random()
|
||||||
|
|
||||||
|
//private fun negativeAck(): String = listOf(
|
||||||
|
// "To ma mrzí.",
|
||||||
|
// "Je mi ľúto, že to takto dopadlo.",
|
||||||
|
// "Chápem. Musí to byť náročné.",
|
||||||
|
// "Mrzí ma to počuť. Verím, že sa situácia postupne zlepší.",
|
||||||
|
//).random()
|
||||||
|
|
||||||
|
fun SmallTalk(nextState: State, question: () -> String): State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
askRepeatable(question())
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Ano> {
|
||||||
|
happyNod()
|
||||||
|
furhat.say(positiveAck())
|
||||||
|
delay(1000)
|
||||||
|
goto(nextState)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Rephrase> {
|
||||||
|
handleRephrase(it.intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
// if (handleRepeat(it.intent)) {
|
||||||
|
// return@onResponse
|
||||||
|
// }
|
||||||
|
if (handleStop(it.intent, "system", "small_talk", it.text ?: "")) {
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
happyNod()
|
||||||
|
furhat.say(neutralAck())
|
||||||
|
delay(1000)
|
||||||
|
goto(nextState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SmallTalk(question: () -> String): State = SmallTalk(TimeTrainingSuccess, question)
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.callProxyRespond
|
||||||
|
import furhatos.app.blank.flow.main.supporting.general.isProxyAvailable
|
||||||
|
import furhatos.flow.kotlin.FlowControlRunner
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
|
||||||
|
fun FlowControlRunner.sayPhraseForWrongAnswer() {
|
||||||
|
littleSad()
|
||||||
|
furhat.say {
|
||||||
|
random {
|
||||||
|
+"Nie celkom, ale nevadí. "
|
||||||
|
+"Tentoraz to ešte nesedí. "
|
||||||
|
+"Ešte to nie je správne. "
|
||||||
|
+"Nie úplne, ale sme blízko. "
|
||||||
|
+"Ešte sa netrafili, ale je to v poriadku. "
|
||||||
|
+"Zatiaľ to nesedí. "
|
||||||
|
+"Nie je to správne, no vôbec to neprekáža. "
|
||||||
|
+"Bohužiaľ, opäť je to nesprávne. Ale nič sa nedeje!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FlowControlRunner.explainWhyWrong(
|
||||||
|
question: String,
|
||||||
|
correctAnswer: String,
|
||||||
|
userAnswer: String,
|
||||||
|
attempt: Int
|
||||||
|
) {
|
||||||
|
if (attempt >= 2) {
|
||||||
|
sayPhraseForWrongAnswer()
|
||||||
|
|
||||||
|
val ctx = mapOf(
|
||||||
|
"question" to question,
|
||||||
|
"correct_answer" to correctAnswer,
|
||||||
|
"user_answer" to userAnswer,
|
||||||
|
"attempt" to attempt
|
||||||
|
)
|
||||||
|
|
||||||
|
val gptText = if (isProxyAvailable()){
|
||||||
|
callProxyRespond(
|
||||||
|
userText = userAnswer,
|
||||||
|
task = "explain_wrong",
|
||||||
|
context = ctx
|
||||||
|
)
|
||||||
|
} else null
|
||||||
|
|
||||||
|
if (!gptText.isNullOrBlank()) {
|
||||||
|
furhat.say(gptText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,181 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting.general
|
||||||
|
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
// IP PC, where is running FastAPI-proxy.
|
||||||
|
private const val PROXY_URL = "http://127.0.0.1:8000"
|
||||||
|
|
||||||
|
val PROXY_BASE_URL: String =
|
||||||
|
System.getenv("PROXY_BASE_URL")
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
?: System.getProperty("proxy.baseUrl")
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
?: PROXY_URL
|
||||||
|
|
||||||
|
|
||||||
|
fun isProxyAvailable(): Boolean {
|
||||||
|
return try {
|
||||||
|
val url = URL("$PROXY_BASE_URL/test")
|
||||||
|
val conn = (url.openConnection() as HttpURLConnection).apply {
|
||||||
|
requestMethod = "GET"
|
||||||
|
setRequestProperty("Accept", "application/json")
|
||||||
|
connectTimeout = 500
|
||||||
|
readTimeout = 500
|
||||||
|
}
|
||||||
|
|
||||||
|
val code = conn.responseCode
|
||||||
|
code in 200..299
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun callProxyRespond(
|
||||||
|
userText: String,
|
||||||
|
task: String,
|
||||||
|
context: Map<String, Any?> = emptyMap()
|
||||||
|
): String? {
|
||||||
|
val url = URL("$PROXY_BASE_URL/respond")
|
||||||
|
val conn = (url.openConnection() as HttpURLConnection).apply {
|
||||||
|
requestMethod = "POST"
|
||||||
|
setRequestProperty("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
setRequestProperty("Accept", "application/json")
|
||||||
|
connectTimeout = 4000
|
||||||
|
readTimeout = 4000
|
||||||
|
doOutput = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val safeText = jsonEscape(userText)
|
||||||
|
val safeTask = jsonEscape(task)
|
||||||
|
val ctxJson = mapToJson(context)
|
||||||
|
|
||||||
|
// context вставляем как JSON-объект, а не строкой
|
||||||
|
val body = """{"user_text":"$safeText","task":"$safeTask","context":$ctxJson}"""
|
||||||
|
|
||||||
|
return try {
|
||||||
|
conn.outputStream.use { it.write(body.toByteArray(Charsets.UTF_8)) }
|
||||||
|
|
||||||
|
val code = conn.responseCode
|
||||||
|
val stream = if (code in 200..299) conn.inputStream else conn.errorStream
|
||||||
|
val raw = stream?.bufferedReader(Charsets.UTF_8)?.use { it.readText() }
|
||||||
|
|
||||||
|
println("Proxy respond: $PROXY_BASE_URL/respond -> HTTP $code")
|
||||||
|
println("Proxy raw response: $raw")
|
||||||
|
|
||||||
|
if (code !in 200..299 || raw.isNullOrBlank()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Простой парсинг поля "answer"
|
||||||
|
val match = Regex("\"answer\"\\s*:\\s*\"(.*?)\"").find(raw) ?: return null
|
||||||
|
match.groupValues[1]
|
||||||
|
.replace("\\n", "\n")
|
||||||
|
.replace("\\\"", "\"")
|
||||||
|
.replace("\\\\", "\\")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Proxy request failed: ${e.message}")
|
||||||
|
null
|
||||||
|
} finally {
|
||||||
|
conn.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun jsonEscape(s: String): String =
|
||||||
|
s.replace("\\", "\\\\")
|
||||||
|
.replace("\"", "\\\"")
|
||||||
|
.replace("\n", "\\n")
|
||||||
|
.replace("\r", "\\r")
|
||||||
|
.replace("\t", "\\t")
|
||||||
|
|
||||||
|
private fun valueToJson(v: Any?): String = when (v) {
|
||||||
|
null -> "null"
|
||||||
|
is String -> "\"${jsonEscape(v)}\""
|
||||||
|
is Number, is Boolean -> v.toString()
|
||||||
|
is Map<*, *> -> mapToJson(v.entries.associate { it.key.toString() to it.value })
|
||||||
|
is List<*> -> v.joinToString(prefix = "[", postfix = "]") { valueToJson(it) }
|
||||||
|
else -> "\"${jsonEscape(v.toString())}\""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapToJson(map: Map<String, Any?>): String =
|
||||||
|
map.entries.joinToString(prefix = "{", postfix = "}") { (k, v) ->
|
||||||
|
"\"${jsonEscape(k)}\":${valueToJson(v)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendLogEvent(
|
||||||
|
createdAt: String,
|
||||||
|
sessionId: String,
|
||||||
|
game: String,
|
||||||
|
phase: String,
|
||||||
|
question: String,
|
||||||
|
attempt: Int,
|
||||||
|
result: String,
|
||||||
|
userAnswer: String? = null,
|
||||||
|
correctAnswer: String? = null,
|
||||||
|
hintUsed: Boolean = false,
|
||||||
|
): Boolean {
|
||||||
|
val url = URL("$PROXY_BASE_URL/log")
|
||||||
|
val conn = (url.openConnection() as HttpURLConnection).apply {
|
||||||
|
requestMethod = "POST"
|
||||||
|
setRequestProperty("Content-Type", "application/json")
|
||||||
|
connectTimeout = 700
|
||||||
|
readTimeout = 1000
|
||||||
|
doOutput = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val payload = mapOf(
|
||||||
|
"session_id" to sessionId,
|
||||||
|
"game" to game,
|
||||||
|
"phase" to phase,
|
||||||
|
"question" to question,
|
||||||
|
"attempt" to attempt,
|
||||||
|
"result" to result,
|
||||||
|
"user_answer" to userAnswer,
|
||||||
|
"correct_answer" to correctAnswer,
|
||||||
|
"hint_used" to hintUsed,
|
||||||
|
"created_at" to createdAt
|
||||||
|
)
|
||||||
|
|
||||||
|
val body = mapToJson(payload)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
conn.outputStream.use { it.write(body.toByteArray(Charsets.UTF_8)) }
|
||||||
|
|
||||||
|
val code = conn.responseCode
|
||||||
|
println("Proxy log: $PROXY_BASE_URL/log -> HTTP $code")
|
||||||
|
|
||||||
|
code in 200..299
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("LOG SEND ERROR: ${e.message}")
|
||||||
|
false
|
||||||
|
} finally {
|
||||||
|
conn.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendLogJsonLine(jsonLine: String): Boolean {
|
||||||
|
val url = URL("$PROXY_BASE_URL/log")
|
||||||
|
val conn = (url.openConnection() as HttpURLConnection).apply {
|
||||||
|
requestMethod = "POST"
|
||||||
|
setRequestProperty("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
setRequestProperty("Accept", "application/json")
|
||||||
|
connectTimeout = 600
|
||||||
|
readTimeout = 900
|
||||||
|
doOutput = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
conn.outputStream.use { it.write(jsonLine.toByteArray(Charsets.UTF_8)) }
|
||||||
|
|
||||||
|
val code = conn.responseCode
|
||||||
|
println("sendLogJsonLine -> HTTP $code")
|
||||||
|
|
||||||
|
code in 200..299
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("sendLogJsonLine ERROR: ${e.message}")
|
||||||
|
false
|
||||||
|
} finally {
|
||||||
|
conn.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting.general
|
||||||
|
|
||||||
|
object TrainingMenuFlags {
|
||||||
|
var hasTrainedOnce: Boolean = false
|
||||||
|
|
||||||
|
var allTimeQuestionsCompleted: Boolean = false
|
||||||
|
var allAttentionQuestionsCompleted: Boolean = false
|
||||||
|
var allMemoryQuestionsCompleted: Boolean = false
|
||||||
|
|
||||||
|
fun resetAll() {
|
||||||
|
allTimeQuestionsCompleted = false
|
||||||
|
allAttentionQuestionsCompleted = false
|
||||||
|
allMemoryQuestionsCompleted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allExercisesCompleted(): Boolean =
|
||||||
|
allTimeQuestionsCompleted && allAttentionQuestionsCompleted && allMemoryQuestionsCompleted
|
||||||
|
}
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting.general
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.StartQuestion
|
||||||
|
import furhatos.app.blank.flow.main.handlers.Goodbye
|
||||||
|
import furhatos.app.blank.flow.main.handlers.askRepeatable
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRepeat
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleRephrase
|
||||||
|
import furhatos.app.blank.flow.main.handlers.handleStop
|
||||||
|
import furhatos.app.blank.flow.main.handlers.lastPhrase
|
||||||
|
import furhatos.app.blank.flow.main.supporting.ReadyToTrain
|
||||||
|
import furhatos.app.blank.flow.main.supporting.empathy
|
||||||
|
import furhatos.app.blank.flow.main.supporting.neutralAck
|
||||||
|
import furhatos.app.blank.flow.main.supporting.veryHappy
|
||||||
|
import furhatos.flow.kotlin.State
|
||||||
|
import furhatos.flow.kotlin.furhat
|
||||||
|
import furhatos.flow.kotlin.onNoResponse
|
||||||
|
import furhatos.flow.kotlin.onResponse
|
||||||
|
import furhatos.flow.kotlin.state
|
||||||
|
|
||||||
|
data class SmallTalkContext(
|
||||||
|
val exercise: String,
|
||||||
|
val topic: String,
|
||||||
|
val subtopic: String? = null,
|
||||||
|
val targetWord: String? = null,
|
||||||
|
val responseMode: String = "open" // open / yes_no
|
||||||
|
)
|
||||||
|
|
||||||
|
fun requestSmallTalk(context: SmallTalkContext): String {
|
||||||
|
if (!isProxyAvailable()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return callProxyRespond(
|
||||||
|
userText = "Vygeneruj small talk otázku",
|
||||||
|
task = "smalltalk",
|
||||||
|
context = mapOf(
|
||||||
|
"exercise" to context.exercise,
|
||||||
|
"topic" to context.topic,
|
||||||
|
"subtopic" to context.subtopic,
|
||||||
|
"target_word" to context.targetWord,
|
||||||
|
"response_mode" to context.responseMode
|
||||||
|
)
|
||||||
|
) ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun genericSmallTalk(
|
||||||
|
context: SmallTalkContext,
|
||||||
|
nextState: State,
|
||||||
|
fallbackQuestion: String = "Ako sa dnes máte?",
|
||||||
|
preparedQuestion: String? = null
|
||||||
|
): State = state(Parent) {
|
||||||
|
|
||||||
|
onEntry {
|
||||||
|
val generated = preparedQuestion ?: requestSmallTalk(context)
|
||||||
|
val finalQuestion = if (generated.isNotBlank()) generated else fallbackQuestion
|
||||||
|
askRepeatable(finalQuestion)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse {
|
||||||
|
if (handleRephrase(it.intent)) return@onResponse
|
||||||
|
// if (handleRepeat(it.intent)) return@onResponse
|
||||||
|
|
||||||
|
if (handleStop(it.intent, "time", "small_talk_proxy", it.text ?: "")) {
|
||||||
|
TrainingMenuFlags.hasTrainedOnce = true
|
||||||
|
goto(ReadyToTrain(StartQuestion, Goodbye))
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
val reaction = requestSmallTalkReaction(
|
||||||
|
userAnswer = it.text,
|
||||||
|
robotQuestion = lastPhrase
|
||||||
|
)
|
||||||
|
|
||||||
|
if (reaction != null) {
|
||||||
|
when (reaction.type) {
|
||||||
|
"positive" -> veryHappy()
|
||||||
|
else -> empathy()
|
||||||
|
}
|
||||||
|
furhat.say(reaction.text)
|
||||||
|
} else {
|
||||||
|
empathy()
|
||||||
|
furhat.say(neutralAck())
|
||||||
|
}
|
||||||
|
|
||||||
|
goto(nextState)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNoResponse {
|
||||||
|
goto(nextState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------
|
||||||
|
// Reactions
|
||||||
|
//----------------------------------------------------------
|
||||||
|
data class SmallTalkReaction(
|
||||||
|
val type: String,
|
||||||
|
val text: String
|
||||||
|
)
|
||||||
|
|
||||||
|
fun requestSmallTalkReaction(
|
||||||
|
userAnswer: String,
|
||||||
|
robotQuestion: String?
|
||||||
|
): SmallTalkReaction? {
|
||||||
|
if (!isProxyAvailable()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val raw = callProxyRespond(
|
||||||
|
userText = userAnswer,
|
||||||
|
task = "reaction",
|
||||||
|
context = mapOf(
|
||||||
|
"question" to robotQuestion,
|
||||||
|
"user_answer" to userAnswer
|
||||||
|
)
|
||||||
|
) ?: return null
|
||||||
|
|
||||||
|
val parts = raw.split("||", limit = 2)
|
||||||
|
if (parts.size < 2) return null
|
||||||
|
|
||||||
|
val type = parts[0].trim().lowercase()
|
||||||
|
val text = parts[1].trim()
|
||||||
|
|
||||||
|
if (text.isBlank()) return null
|
||||||
|
|
||||||
|
return SmallTalkReaction(type = type, text = text)
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting.general
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Parent
|
||||||
|
import furhatos.app.blank.flow.main.supporting.littleSad
|
||||||
|
import furhatos.flow.kotlin.State
|
||||||
|
import furhatos.flow.kotlin.state
|
||||||
|
|
||||||
|
val Test: State = state(Parent) {
|
||||||
|
onEntry {
|
||||||
|
littleSad()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
package furhatos.app.blank.flow.main.supporting.general
|
||||||
|
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
data class WordEntry(
|
||||||
|
val canonical: String,
|
||||||
|
val theme: String,
|
||||||
|
val difficulty: Int,
|
||||||
|
val variants: Set<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
object WordBank {
|
||||||
|
|
||||||
|
private const val RESOURCE_PATH = "wordbank.csv"
|
||||||
|
|
||||||
|
val entries: List<WordEntry> by lazy { loadCsv(RESOURCE_PATH) }
|
||||||
|
|
||||||
|
fun pickSequence(
|
||||||
|
length: Int,
|
||||||
|
allowedThemes: Set<String>? = null,
|
||||||
|
maxDifficulty: Int? = null,
|
||||||
|
rng: Random = Random.Default
|
||||||
|
): List<WordEntry> {
|
||||||
|
val pool = entries.asSequence()
|
||||||
|
.filter { allowedThemes == null || it.theme in allowedThemes }
|
||||||
|
.filter { maxDifficulty == null || it.difficulty <= maxDifficulty }
|
||||||
|
.distinctBy { it.canonical.lowercase() }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
require(pool.size >= length) {
|
||||||
|
"WordBank: There aren't enough words for the filters. Need to $length, there is ${pool.size}."
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool.shuffled(rng).take(length)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadCsv(path: String): List<WordEntry> {
|
||||||
|
val stream = Thread.currentThread().contextClassLoader.getResourceAsStream(path)
|
||||||
|
?: error("WordBank: Resource is not found '$path' в src/main/resources")
|
||||||
|
|
||||||
|
BufferedReader(InputStreamReader(stream, Charsets.UTF_8)).use { br ->
|
||||||
|
return br.lineSequence()
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.filter { !it.startsWith("#") }
|
||||||
|
.mapIndexed { idx, line ->
|
||||||
|
// canonical,theme,difficulty,variants
|
||||||
|
val parts = line.split(',', limit = 4)
|
||||||
|
require(parts.size >= 3) {
|
||||||
|
"WordBank CSV error (line ${idx + 1}): At least 3 fields 'canonical;theme;difficulty'"
|
||||||
|
}
|
||||||
|
|
||||||
|
val canonical = parts[0].trim()
|
||||||
|
val theme = parts[1].trim()
|
||||||
|
val difficulty = parts[2].trim().toIntOrNull()
|
||||||
|
?: error("WordBank CSV error (line ${idx + 1}): difficulty is not a number")
|
||||||
|
|
||||||
|
val variantsRaw = parts.getOrNull(3)?.trim().orEmpty()
|
||||||
|
val variants = variantsRaw
|
||||||
|
.split('|')
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.toMutableSet()
|
||||||
|
|
||||||
|
variants.add(canonical)
|
||||||
|
|
||||||
|
WordEntry(
|
||||||
|
canonical = canonical,
|
||||||
|
theme = theme,
|
||||||
|
difficulty = difficulty,
|
||||||
|
variants = variants
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/main/kotlin/furhatos/app/blank/flow/parent.kt
Normal file
56
src/main/kotlin/furhatos/app/blank/flow/parent.kt
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package furhatos.app.blank.flow
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.main.handlers.Goodbye
|
||||||
|
import furhatos.app.blank.flow.main.handlers.lastPhrase
|
||||||
|
import furhatos.app.blank.flow.main.supporting.SmileIdle
|
||||||
|
import furhatos.flow.kotlin.*
|
||||||
|
import furhatos.app.blank.nlu.base_answer.Repeat
|
||||||
|
|
||||||
|
val Parent: State = state {
|
||||||
|
|
||||||
|
onEntry(inherit = true, priority = true) {
|
||||||
|
furhat.param.noSpeechTimeout = 60_000
|
||||||
|
furhat.param.endSilTimeout = 1_000
|
||||||
|
furhat.param.maxSpeechTimeout = 120_000
|
||||||
|
furhat.gesture(SmileIdle, async = true)
|
||||||
|
propagate()
|
||||||
|
}
|
||||||
|
|
||||||
|
onUserEnter(instant = true) {
|
||||||
|
when { // "it" is the user that entered
|
||||||
|
furhat.isAttendingUser -> furhat.glance(it) // Glance at new users entering
|
||||||
|
!furhat.isAttendingUser -> furhat.attend(it) // Attend user if not attending anyone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUserLeave(instant = true) {
|
||||||
|
when {
|
||||||
|
!users.hasAny() -> { // last user left
|
||||||
|
goto(Goodbye)
|
||||||
|
}
|
||||||
|
furhat.isAttending(it) -> furhat.attend(users.other) // current user left
|
||||||
|
!furhat.isAttending(it) -> furhat.glance(it.head.location) // other user left, just glance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse<Repeat> {
|
||||||
|
val phrase = lastPhrase
|
||||||
|
if (phrase != null) {
|
||||||
|
furhat.say("Samozrejme, zopakujem. ")
|
||||||
|
furhat.ask(phrase)
|
||||||
|
} else {
|
||||||
|
furhat.say("Momentálne nemám čo zopakovať.")
|
||||||
|
furhat.listen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// onResponse<StopTraining> {
|
||||||
|
// handleStop(it.intent)
|
||||||
|
// }
|
||||||
|
|
||||||
|
onNoResponse {
|
||||||
|
furhat.say("Je v poriadku, môžete si dať čas. Keď budete pripravení, odpovedzte, prosím.")
|
||||||
|
reentry()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
src/main/kotlin/furhatos/app/blank/main.kt
Normal file
18
src/main/kotlin/furhatos/app/blank/main.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package furhatos.app.blank
|
||||||
|
|
||||||
|
import furhatos.app.blank.flow.Init
|
||||||
|
import furhatos.flow.kotlin.Flow
|
||||||
|
import furhatos.skills.Skill
|
||||||
|
|
||||||
|
class BlankSkill : Skill() {
|
||||||
|
override fun start() {
|
||||||
|
Flow().run(Init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
Skill.main(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
24
src/main/kotlin/furhatos/app/blank/nlu/AttentionTraining.kt
Normal file
24
src/main/kotlin/furhatos/app/blank/nlu/AttentionTraining.kt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package furhatos.app.blank.nlu
|
||||||
|
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
class AttentionTraining : Intent() {
|
||||||
|
override fun getExamples(lang: Language) = listOf(
|
||||||
|
"pozornosť",
|
||||||
|
"trénovať pozornosť",
|
||||||
|
"chcem trénovať pozornosť",
|
||||||
|
"chcem si precvičiť pozornosť",
|
||||||
|
"chcem skúšať pozornosť",
|
||||||
|
"chcem skúsiť pozornosť",
|
||||||
|
"chcem otestovať pozornosť",
|
||||||
|
"otestovať pozornosť",
|
||||||
|
"skúšať pozornosť",
|
||||||
|
"test pozornosti",
|
||||||
|
"trénovať pozornosť teraz",
|
||||||
|
"poďme na pozornosť",
|
||||||
|
"ideme na pozornosť",
|
||||||
|
"chcem test pozornosti",
|
||||||
|
"chcem precvičiť sústredenie"
|
||||||
|
)
|
||||||
|
}
|
||||||
25
src/main/kotlin/furhatos/app/blank/nlu/MemoryTraining.kt
Normal file
25
src/main/kotlin/furhatos/app/blank/nlu/MemoryTraining.kt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package furhatos.app.blank.nlu
|
||||||
|
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
class MemoryTraining : Intent() {
|
||||||
|
override fun getExamples(lang: Language) = listOf(
|
||||||
|
"pamäť",
|
||||||
|
"cvičenie na pamäť",
|
||||||
|
"pamäťové cvičenie",
|
||||||
|
"trénovať pamäť",
|
||||||
|
"precvičiť pamäť",
|
||||||
|
"chcem trénovať pamäť",
|
||||||
|
"tréning pamäti",
|
||||||
|
"skúsme si pamäť",
|
||||||
|
"chcem skúsiť čas",
|
||||||
|
"chcem si vyskúšať zapamätanie slov",
|
||||||
|
"Ideme na pamäťové slová",
|
||||||
|
"Môžeme začať s pamäťou?",
|
||||||
|
"Chcem trénovať pamäť so slovami",
|
||||||
|
"Rád by som si skúsil pamäť",
|
||||||
|
"Rada by som si skúsila pamäť",
|
||||||
|
"chcem otestovať pamäť"
|
||||||
|
)
|
||||||
|
}
|
||||||
18
src/main/kotlin/furhatos/app/blank/nlu/TimeTraining.kt
Normal file
18
src/main/kotlin/furhatos/app/blank/nlu/TimeTraining.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package furhatos.app.blank.nlu
|
||||||
|
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
class TimeTraining : Intent() {
|
||||||
|
override fun getExamples(lang: Language) = listOf(
|
||||||
|
"orientáciu v čase",
|
||||||
|
"čas",
|
||||||
|
"trénovať čas",
|
||||||
|
"chcem trénovať čas",
|
||||||
|
"chcem trénovať orientáciu v čase",
|
||||||
|
"povedať čas",
|
||||||
|
"chcem skúšať čas",
|
||||||
|
"chcem skúsiť čas",
|
||||||
|
"chcem otestovať čas"
|
||||||
|
)
|
||||||
|
}
|
||||||
30
src/main/kotlin/furhatos/app/blank/nlu/base_answer/Ano.kt
Normal file
30
src/main/kotlin/furhatos/app/blank/nlu/base_answer/Ano.kt
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package furhatos.app.blank.nlu.base_answer
|
||||||
|
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
class Ano : Intent() {
|
||||||
|
override fun getExamples(lang: Language): List<String> = listOf(
|
||||||
|
"ano",
|
||||||
|
"áno",
|
||||||
|
"hej",
|
||||||
|
"jasné",
|
||||||
|
"samozrejme",
|
||||||
|
"určite",
|
||||||
|
"áno, prosím",
|
||||||
|
"áno, mám",
|
||||||
|
"ok",
|
||||||
|
"pokračujme",
|
||||||
|
"môžeme pokračovať",
|
||||||
|
"poďme ďalej",
|
||||||
|
"áno, prosím",
|
||||||
|
"áno, môžeme",
|
||||||
|
"áno, chcem pokračovať",
|
||||||
|
"chcela by som pokračovať",
|
||||||
|
"chcel by som pokračovať",
|
||||||
|
"chcem pokračovať",
|
||||||
|
"mám rada",
|
||||||
|
"mám rad"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package furhatos.app.blank.nlu.base_answer
|
||||||
|
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
class DontKnow: Intent() {
|
||||||
|
override fun getExamples(lang: Language) = listOf(
|
||||||
|
"Neviem",
|
||||||
|
"Neviem to",
|
||||||
|
"Nie som si istý",
|
||||||
|
"Nie som si istá",
|
||||||
|
"Nepamätám si",
|
||||||
|
"Nemám tušenie.",
|
||||||
|
"Netuším",
|
||||||
|
"Vypadlo mi to z hlavy",
|
||||||
|
"Zabudol som",
|
||||||
|
"Zabudola som",
|
||||||
|
"Neviem to povedať",
|
||||||
|
"Neviem sa rozhodnúť",
|
||||||
|
"Bez tušenia",
|
||||||
|
"Je to pre mňa ťažké",
|
||||||
|
"Neviem si spomenúť"
|
||||||
|
)
|
||||||
|
}
|
||||||
19
src/main/kotlin/furhatos/app/blank/nlu/base_answer/Help.kt
Normal file
19
src/main/kotlin/furhatos/app/blank/nlu/base_answer/Help.kt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package furhatos.app.blank.nlu.base_answer
|
||||||
|
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
class Help: Intent() {
|
||||||
|
override fun getExamples(lang: Language) = listOf(
|
||||||
|
"Potrebujem pomoc",
|
||||||
|
"Môžete mi pomôcť?",
|
||||||
|
"Pomôž mi, prosím",
|
||||||
|
"Prosím o pomoc",
|
||||||
|
"Daj mi nápovedu, prosím",
|
||||||
|
"Nemám tušenie.",
|
||||||
|
"Netuším",
|
||||||
|
"chcem nápovedu",
|
||||||
|
"potrebujem nápovedu",
|
||||||
|
"daj mi napovedu"
|
||||||
|
)
|
||||||
|
}
|
||||||
27
src/main/kotlin/furhatos/app/blank/nlu/base_answer/Nie.kt
Normal file
27
src/main/kotlin/furhatos/app/blank/nlu/base_answer/Nie.kt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package furhatos.app.blank.nlu.base_answer
|
||||||
|
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
class Nie : Intent() {
|
||||||
|
override fun getExamples(lang: Language): List<String> = listOf(
|
||||||
|
"nie",
|
||||||
|
"nie, nemám",
|
||||||
|
"vôbec nie",
|
||||||
|
"ani náhodou",
|
||||||
|
"nie, ďakujem",
|
||||||
|
"nie, prosím",
|
||||||
|
"nie, nechcem",
|
||||||
|
"nechcem pokračovať",
|
||||||
|
"nechcem ďalej",
|
||||||
|
"už nechcem",
|
||||||
|
"stačí",
|
||||||
|
"to stačí",
|
||||||
|
"pre dnešok stačí",
|
||||||
|
"radšej nie",
|
||||||
|
"asi nie",
|
||||||
|
"dnes nie",
|
||||||
|
"môžeme to ukončiť",
|
||||||
|
"chcem skončiť"
|
||||||
|
)
|
||||||
|
}
|
||||||
18
src/main/kotlin/furhatos/app/blank/nlu/base_answer/Repeat.kt
Normal file
18
src/main/kotlin/furhatos/app/blank/nlu/base_answer/Repeat.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package furhatos.app.blank.nlu.base_answer
|
||||||
|
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
class Repeat: Intent() {
|
||||||
|
override fun getExamples(lang: Language) = listOf(
|
||||||
|
"môžeš to zopakovať",
|
||||||
|
"prosím zopakuj",
|
||||||
|
"môžeš to povedať ešte raz",
|
||||||
|
"ešte raz prosím",
|
||||||
|
"nepočul som ťa",
|
||||||
|
"môžeš zopakovať otázku",
|
||||||
|
"čo si povedal",
|
||||||
|
"čo si povedala",
|
||||||
|
"zopakuj"
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package furhatos.app.blank.nlu.base_answer
|
||||||
|
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
class Rephrase : Intent() {
|
||||||
|
override fun getExamples(lang: Language)= listOf(
|
||||||
|
"nerozumiem",
|
||||||
|
"povedz to inak",
|
||||||
|
"môžeš to povedať jednoduchšie",
|
||||||
|
"povedz to prosím inak",
|
||||||
|
"preformuluj to prosím",
|
||||||
|
"ja som tomu nerozumel",
|
||||||
|
"ja som tomu nerozumela",
|
||||||
|
"skúste to povedať inak",
|
||||||
|
"môžeš to vysvetliť jednoduchšie",
|
||||||
|
"nepochopil som",
|
||||||
|
"nepochopila som",
|
||||||
|
"perifrázuj to",
|
||||||
|
"prefrázuj to",
|
||||||
|
"povedz to ešte inak"
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package furhatos.app.blank.nlu.base_answer
|
||||||
|
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
import furhatos.util.Language
|
||||||
|
|
||||||
|
class StopTraining: Intent() {
|
||||||
|
override fun getExamples(lang: Language) = listOf(
|
||||||
|
"zastaviť",
|
||||||
|
"zastav sa",
|
||||||
|
"zastavme sa",
|
||||||
|
"zastavme sa prosím",
|
||||||
|
"stačí",
|
||||||
|
"dosť",
|
||||||
|
"koniec",
|
||||||
|
"ukončiť",
|
||||||
|
"ukonči to",
|
||||||
|
"ukonči cvičenie",
|
||||||
|
"skončiť",
|
||||||
|
"skončime",
|
||||||
|
"môžeme prestať",
|
||||||
|
"prestať",
|
||||||
|
"chcem prestať",
|
||||||
|
"chcem skončiť",
|
||||||
|
"chcem ukončiť cvičenie",
|
||||||
|
"už nechcem pokračovať",
|
||||||
|
"nechcem pokračovať",
|
||||||
|
"nechcem ďalej",
|
||||||
|
"chcela by som skončiť",
|
||||||
|
"chcel by som skončiť",
|
||||||
|
"skončiť",
|
||||||
|
|
||||||
|
// bez diakritiky
|
||||||
|
"chcem skoncit",
|
||||||
|
"chcem prestat",
|
||||||
|
"ukoncit cvicenie",
|
||||||
|
"nechcem pokracovat",
|
||||||
|
"chcela by som skoncit",
|
||||||
|
"chcel by som skoncit"
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package furhatos.app.blank.nlu.other_responses
|
||||||
|
|
||||||
|
import furhatos.util.Language
|
||||||
|
import furhatos.nlu.Intent
|
||||||
|
|
||||||
|
class FeelGood : Intent() {
|
||||||
|
override fun getExamples(lang: Language) = listOf(
|
||||||
|
"Cítim sa dobre",
|
||||||
|
"Cítim sa fajn",
|
||||||
|
"Som v poriadku",
|
||||||
|
"Je mi dobre",
|
||||||
|
"Cítim sa výborne",
|
||||||
|
"dobre",
|
||||||
|
"super"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class FeelBad : Intent() {
|
||||||
|
override fun getExamples(lang: Language) = listOf(
|
||||||
|
"Som unavený",
|
||||||
|
"Som unavená",
|
||||||
|
"Cítim sa unavene",
|
||||||
|
"Necítim sa dobre",
|
||||||
|
"Je mi zle",
|
||||||
|
"Nie veľmi dobré",
|
||||||
|
"zle",
|
||||||
|
"hrozne"
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package furhatos.app.blank.setting
|
||||||
|
|
||||||
|
/** Engagement parameters */
|
||||||
|
const val MAX_NUMBER_OF_USERS = 2 // Max amount of people that Furhat will recognize as users simultaneously
|
||||||
|
const val DISTANCE_TO_ENGAGE = 1.0 // Min distance for people to be recognised as users
|
||||||
290
src/main/realtime/main.py
Normal file
290
src/main/realtime/main.py
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from openai import AsyncOpenAI
|
||||||
|
|
||||||
|
import pymysql
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Config
|
||||||
|
# -----------------------------
|
||||||
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||||||
|
if not OPENAI_API_KEY:
|
||||||
|
# Лучше падать сразу, чтобы не ловить странные ошибки позже
|
||||||
|
raise RuntimeError("Environment variable OPENAI_API_KEY is not set.")
|
||||||
|
|
||||||
|
# Вы можете поменять на нужную realtime-модель
|
||||||
|
REALTIME_MODEL = os.getenv("REALTIME_MODEL", "gpt-realtime")
|
||||||
|
|
||||||
|
BASE_INSTRUCTIONS = os.getenv(
|
||||||
|
"REALTIME_INSTRUCTIONS",
|
||||||
|
# Эти инструкции НЕ должны дублировать весь ваш сценарий из Kotlin.
|
||||||
|
# Это только общий стиль/ограничения.
|
||||||
|
(
|
||||||
|
"Si sociálny robot, ktorý pomáha ľuďom s kognitívno-komunikačnými problémami pomocou krátkych hier a dialógov. "
|
||||||
|
"Vždy odpovedaj po slovensky. "
|
||||||
|
"Odpovedaj veľmi stručne v rozsahu 1–2 viet, priateľsky a s použitím jednoduchého slovníka. "
|
||||||
|
"Nevymýšľaj nové pravidlá hry. Ak nemáš dosť informácií, polož jednu krátku otázku. "
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
print("ENV KEY PRESENT:", bool(os.getenv("OPENAI_API_KEY")))
|
||||||
|
print("MODEL:", os.getenv("REALTIME_MODEL"))
|
||||||
|
|
||||||
|
OUTPUT_MODALITIES = ["text"] # мы используем только текстовый realtime :contentReference[oaicite:1]{index=1}
|
||||||
|
|
||||||
|
MYSQL_HOST = os.getenv("MYSQL_HOST", "127.0.0.1")
|
||||||
|
MYSQL_PORT = int(os.getenv("MYSQL_PORT", "3306"))
|
||||||
|
MYSQL_DATABASE = os.getenv("MYSQL_DATABASE", "furhat_logs")
|
||||||
|
MYSQL_USER = os.getenv("MYSQL_USER", "localhost")
|
||||||
|
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "")
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# HTTP API models
|
||||||
|
# -----------------------------
|
||||||
|
class RespondRequest(BaseModel):
|
||||||
|
# session_id: str = Field(..., description="ID логической сессии (пришлёт Kotlin).")
|
||||||
|
# user_text: str = Field(..., description="Текст пользователя из Furhat STT / onResponse.")
|
||||||
|
user_text: str
|
||||||
|
# Опционально: чтобы Kotlin мог попросить конкретный тип помощи
|
||||||
|
task: Optional[str] = Field(
|
||||||
|
default=None,
|
||||||
|
description="Напр. 'hint', 'rephrase', 'explain_wrong', 'smalltalk' и т.п.",
|
||||||
|
)
|
||||||
|
# Опционально: краткий контекст текущего шага (вопрос, попытки и т.д.)
|
||||||
|
context: Optional[Dict[str, Any]] = Field(default=None)
|
||||||
|
|
||||||
|
|
||||||
|
class RespondResponse(BaseModel):
|
||||||
|
answer: str
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# DB
|
||||||
|
# -----------------------------
|
||||||
|
class LogRequest(BaseModel):
|
||||||
|
session_id: str
|
||||||
|
game: str
|
||||||
|
phase: str
|
||||||
|
question: str
|
||||||
|
attempt: int
|
||||||
|
result: str
|
||||||
|
user_answer: Optional[str] = None
|
||||||
|
correct_answer: Optional[str] = None
|
||||||
|
hint_used: bool = False
|
||||||
|
created_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
def get_db_connection():
|
||||||
|
return pymysql.connect(
|
||||||
|
host=MYSQL_HOST,
|
||||||
|
port=MYSQL_PORT,
|
||||||
|
user=MYSQL_USER,
|
||||||
|
password=MYSQL_PASSWORD,
|
||||||
|
database=MYSQL_DATABASE,
|
||||||
|
charset="utf8mb4",
|
||||||
|
cursorclass=pymysql.cursors.DictCursor,
|
||||||
|
autocommit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Realtime connection manager
|
||||||
|
# -----------------------------
|
||||||
|
app = FastAPI(title="Realtime Proxy", version="0.1.0")
|
||||||
|
|
||||||
|
openai_client = AsyncOpenAI(api_key=OPENAI_API_KEY)
|
||||||
|
|
||||||
|
def _build_prompt(task: Optional[str], context: Optional[Dict[str, Any]]) -> str:
|
||||||
|
ctx = context or {}
|
||||||
|
|
||||||
|
if task == "hint":
|
||||||
|
return (
|
||||||
|
"Daj veľmi jemnú, ale konkretnú nápovedu bez toho, aby si prezradil správnu odpoveď. 1-2 vety"
|
||||||
|
f"Kontekst: {ctx}"
|
||||||
|
)
|
||||||
|
if task == "rephrase":
|
||||||
|
return (
|
||||||
|
"Preformuluj otázku jednoduchšie, použivaj jednoduche slova, ale zachovaj význam, 1-2 vety. NEPREZRAĎ správnu odpoveď. "
|
||||||
|
f"Kontekst: {ctx}"
|
||||||
|
)
|
||||||
|
if task == "explain_wrong":
|
||||||
|
return (
|
||||||
|
"Vysvetli stručne, PREČO odpoveď nie je správna, ale NEPREZRAĎ správnu odpoveď. "
|
||||||
|
"Namiesto toho daj jemnú nápovedu jednou vetou (napríklad „Poplietli ste poradie slov“ alebo „Uviedli ste čas o 2 hodiny skôr, než je aktuálny“). "
|
||||||
|
"Povzbuď na ďalší pokus. "
|
||||||
|
f"Kontekst: {ctx}"
|
||||||
|
)
|
||||||
|
if task == "reaction":
|
||||||
|
return (
|
||||||
|
"Vyhodnoť odpoveď používateľa na small-talk otázku robota. "
|
||||||
|
"Rozhodni iba medzi dvoma typmi reakcie: positive alebo neutral. "
|
||||||
|
"positive použi, ak odpoveď znie príjemne, pozitívne, radostne alebo povzbudivo. "
|
||||||
|
"neutral použi, ak je odpoveď vecná, nejasná, krátka, zmiešaná alebo ju nemožno bezpečne chápať ako pozitívnu. "
|
||||||
|
"Potom vytvor veľmi krátku empatickú reakciu robota v jednoduchej slovenčine. "
|
||||||
|
"Štýl má byť priateľský a úprimný, napríklad: 'To ma veľmi teší.', 'Ďakujem, že ste mi to povedali.', atď. Nepoužívaj ich stále, použivaj rôzne"
|
||||||
|
"Nevysvetľuj svoje rozhodnutie. "
|
||||||
|
"Výstup musí mať presne formát: typ||reakcia "
|
||||||
|
"kde typ je iba positive alebo neutral. "
|
||||||
|
f"Kontekst: {ctx}"
|
||||||
|
)
|
||||||
|
if task == "smalltalk":
|
||||||
|
exercise = ctx.get("exercise", "")
|
||||||
|
topic = ctx.get("topic", "")
|
||||||
|
subtopic = ctx.get("subtopic", "")
|
||||||
|
target_word = ctx.get("target_word", "")
|
||||||
|
response_mode = ctx.get("response_mode", "open")
|
||||||
|
return (
|
||||||
|
"Polož krátku prirodzenú small-talk otázku súvisiacu s témou aktuálneho cvičenia. "
|
||||||
|
"Použi jednoduché slová, 1-2 vety. "
|
||||||
|
"Nemeň tému a nevymýšľaj nové pravidlá hry. "
|
||||||
|
"Nevypisuj viac otázok naraz. "
|
||||||
|
f"Typ cvičenia: {exercise}. "
|
||||||
|
f"Téma: {topic}. "
|
||||||
|
f"Podtéma: {subtopic}. "
|
||||||
|
f"Kľúčové slovo: {target_word}. "
|
||||||
|
f"Formát odpovede používateľa: {response_mode}. "
|
||||||
|
"Ak je formát 'yes_no', vytvor otázku, na ktorú sa prirodzene odpovedá áno alebo nie. "
|
||||||
|
"Ak je formát 'open', vytvor otvorenú každodennú otázku. "
|
||||||
|
)
|
||||||
|
|
||||||
|
# default
|
||||||
|
return f"Odpovedaj stručne a vecne. Kontekst: {ctx}"
|
||||||
|
|
||||||
|
|
||||||
|
async def _ask_realtime(user_text: str, task: Optional[str], context: Optional[Dict[str, Any]]) -> str:
|
||||||
|
per_turn_instructions = _build_prompt(task, context)
|
||||||
|
|
||||||
|
async with openai_client.realtime.connect(model=REALTIME_MODEL) as conn:
|
||||||
|
await conn.session.update(
|
||||||
|
session={
|
||||||
|
"type": "realtime",
|
||||||
|
"output_modalities": ["text"],
|
||||||
|
"instructions": BASE_INSTRUCTIONS,
|
||||||
|
"tool_choice": "none",
|
||||||
|
"tools": [],
|
||||||
|
"audio": {"input": {"turn_detection": None}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await conn.conversation.item.create(
|
||||||
|
item={
|
||||||
|
"type": "message",
|
||||||
|
"role": "user",
|
||||||
|
"content": [{"type": "input_text", "text": user_text}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await conn.response.create(
|
||||||
|
response={
|
||||||
|
"instructions": per_turn_instructions,
|
||||||
|
"max_output_tokens": 70,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
|
||||||
|
async for event in conn:
|
||||||
|
# print("EV:", event.type)
|
||||||
|
|
||||||
|
if event.type in ("response.output_text.delta", "response.text.delta"):
|
||||||
|
chunks.append(event.delta)
|
||||||
|
|
||||||
|
elif event.type == "response.done":
|
||||||
|
# ЛОГИ — всегда, чтобы видеть, что реально пришло в DONE
|
||||||
|
resp = getattr(event, "response", None)
|
||||||
|
output = getattr(resp, "output", None) or []
|
||||||
|
# print("DONE output len:", len(output))
|
||||||
|
# print("DONE output raw:", output)
|
||||||
|
# print("DONE response obj:", resp)
|
||||||
|
|
||||||
|
# Если дельт не было — пробуем достать финальный текст из response.output
|
||||||
|
if not chunks:
|
||||||
|
for item in output:
|
||||||
|
if getattr(item, "type", None) == "message":
|
||||||
|
for c in getattr(item, "content", []) or []:
|
||||||
|
ctype = getattr(c, "type", None)
|
||||||
|
if ctype in ("output_text", "text"):
|
||||||
|
txt = getattr(c, "text", "") or ""
|
||||||
|
if txt:
|
||||||
|
chunks.append(txt)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
elif event.type == "error":
|
||||||
|
raise RuntimeError(f"Realtime error: {event.error}")
|
||||||
|
|
||||||
|
answer = "".join(chunks).strip()
|
||||||
|
return answer or "Prepáčte, môžete to prosím zopakovať?"
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# FastAPI endpoints
|
||||||
|
# -----------------------------
|
||||||
|
@app.get("/test")
|
||||||
|
async def health():
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/respond", response_model=RespondResponse)
|
||||||
|
async def respond(req: RespondRequest):
|
||||||
|
try:
|
||||||
|
answer = await _ask_realtime(req.user_text, req.task, req.context)
|
||||||
|
print("RESPOND:", req.user_text, req.task)
|
||||||
|
return RespondResponse(answer=answer)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.post("/reset")
|
||||||
|
async def reset():
|
||||||
|
print("RESET called")
|
||||||
|
return {"status": "reset"}
|
||||||
|
|
||||||
|
@app.post("/log")
|
||||||
|
async def log_event(req: LogRequest):
|
||||||
|
try:
|
||||||
|
conn = get_db_connection()
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO session_logs (
|
||||||
|
session_id,
|
||||||
|
game,
|
||||||
|
phase,
|
||||||
|
question,
|
||||||
|
attempt,
|
||||||
|
result,
|
||||||
|
user_answer,
|
||||||
|
correct_answer,
|
||||||
|
hint_used,
|
||||||
|
created_at
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
req.session_id,
|
||||||
|
req.game,
|
||||||
|
req.phase,
|
||||||
|
req.question,
|
||||||
|
req.attempt,
|
||||||
|
req.result,
|
||||||
|
req.user_answer,
|
||||||
|
req.correct_answer,
|
||||||
|
req.hint_used,
|
||||||
|
req.created_at or datetime.utcnow(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("LOG ERROR:", repr(e))
|
||||||
|
traceback.print_exc()
|
||||||
|
raise HTTPException(status_code=500, detail=f"log insert failed: {str(e)}")
|
||||||
4
src/main/realtime/requirements.txt
Normal file
4
src/main/realtime/requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn[standard]
|
||||||
|
openai
|
||||||
|
python-dotenv
|
||||||
159
src/main/resources/wordbank.csv
Normal file
159
src/main/resources/wordbank.csv
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
|
||||||
|
jablko,ovocie,1,jablka|jablk|jablky
|
||||||
|
hruška,ovocie,1,hruska|hruška|hrušk
|
||||||
|
banán,ovocie,1,banan|banán|banana
|
||||||
|
pomaranč,ovocie,2,pomaranc|pomaranče
|
||||||
|
citrón,ovocie,2,citron|citróny|citróne
|
||||||
|
jahoda,bobuľa,1,jahoda|jahod|jahda
|
||||||
|
malina,bobuľa,2,malina|maliny
|
||||||
|
orech,jedlo,1,orech
|
||||||
|
chlieb,jedlo,1,chlieb
|
||||||
|
maslo,jedlo,1,maslo
|
||||||
|
syr,jedlo,1,syr
|
||||||
|
mlieko,nápoje,1,mlieko
|
||||||
|
voda,nápoje,1,voda
|
||||||
|
čaj,nápoje,1,caj|čaj
|
||||||
|
káva,nápoje,1,kava|káva
|
||||||
|
polievka,jedlo,2,polievka
|
||||||
|
ryža,jedlo,2,ryza|ryža
|
||||||
|
cestovina,jedlo,2,cestovina
|
||||||
|
mäso,jedlo,1,maso|mäso
|
||||||
|
kurča,jedlo,2,kurca|kurča
|
||||||
|
ryba,jedlo,1,ryba
|
||||||
|
vajce,jedlo,1,vajce
|
||||||
|
soľ,prísady,1,sol|soľ
|
||||||
|
cukor,prísady,1,cukor
|
||||||
|
med,sladké,1,med
|
||||||
|
jogurt,jedlo,2,jogurt
|
||||||
|
koláč,sladké,2,kolac|koláč
|
||||||
|
čokoláda,sladké,2,cokolada|čokoláda
|
||||||
|
zmrzlina,sladké,2,zmrzlina
|
||||||
|
paradajka,zelenina,1,paradajka
|
||||||
|
uhorka,zelenina,1,uhorka
|
||||||
|
zemiak,zelenina,1,zemiaky
|
||||||
|
mrkva,zelenina,1,mrkva
|
||||||
|
cibuľa,zelenina,2,cibula|cibuľa
|
||||||
|
cesnak,zelenina,1,cesnak
|
||||||
|
paprika,zelenina,1,paprika
|
||||||
|
šalát,zelenina,2,salat|šalát
|
||||||
|
fazuľa,zelenina,2,fazula|fazuľa
|
||||||
|
hrášok,zelenina,2,hrasok|hrášok
|
||||||
|
múka,jedlo,2,muka|múka
|
||||||
|
ocot,prísady,2,ocot
|
||||||
|
stôl,domáce potreby,1,stol|stôl
|
||||||
|
stolička,domáce potreby,1,stolicka|stolička
|
||||||
|
posteľ,domáce potreby,2,postel|posteľ
|
||||||
|
vankúš,domáce potreby,2,vankus|vankúš
|
||||||
|
deka,domáce potreby,1,deka
|
||||||
|
skriňa,domáce potreby,2,skrina|skriňa
|
||||||
|
dvere,dom,1,dvere
|
||||||
|
okno,dom,1,okno
|
||||||
|
lampa,technika,1,lampa
|
||||||
|
koberec,domáce potreby,2,koberec
|
||||||
|
zrkadlo,domáce potreby,2,zrkadlo
|
||||||
|
kľúč,dom,1,kluc|kľúč
|
||||||
|
zásuvka,dom,2,zasuvka|zásuvka
|
||||||
|
polica,dom,1,polica
|
||||||
|
chladnička,dom,3,chladnicka|chladnička
|
||||||
|
rúra,dom,2,rura|rúra
|
||||||
|
sporák,dom,3,sporak|sporák
|
||||||
|
umývadlo,hygiena,3,umyvadlo|umývadlo
|
||||||
|
sprcha,hygiena,2,sprcha
|
||||||
|
vaňa,dom,2,vana|vaňa
|
||||||
|
uterák,dom,2,uterak|uterák
|
||||||
|
mydlo,hygiena,1,mydlo
|
||||||
|
šampón,hygiena,3,sampón|sampon|šampón
|
||||||
|
práčka,technika,3,pracka|práčka
|
||||||
|
žehlička,technika,3,zehlicka|žehlička
|
||||||
|
televízor,druh zábavy,3,televizor|televízor
|
||||||
|
rádio,technika,2,radio|rádio
|
||||||
|
počítač,technika,3,pocitac|počítač
|
||||||
|
telefón,technika,2,telefon|telefón
|
||||||
|
hodiny,technika,2,hodiny
|
||||||
|
vypínač,technika,3,vypinac|vypínač
|
||||||
|
batéria,technika,2,bateria|batéria
|
||||||
|
strom,príroda,1,strom
|
||||||
|
kvet,príroda,1,kvet
|
||||||
|
tráva,príroda,1,trava|tráva
|
||||||
|
list,príroda,1,list
|
||||||
|
vetva,príroda,1,vetva
|
||||||
|
rieka,príroda,1,rieka
|
||||||
|
jazero,príroda,2,jazero
|
||||||
|
more,príroda,1,more
|
||||||
|
hora,príroda,1,hora
|
||||||
|
les,príroda,1,les
|
||||||
|
lúka,príroda,2,luka|lúka
|
||||||
|
dážď,príroda,2,dazd|dážď
|
||||||
|
sneh,príroda,1,sneh
|
||||||
|
vietor,príroda,1,vietor
|
||||||
|
slnko,príroda,1,slnko
|
||||||
|
oblak,príroda,1,oblak
|
||||||
|
hviezda,príroda,2,hviezda
|
||||||
|
mesiac,príroda,1,mesiac
|
||||||
|
kameň,príroda,2,kamen|kameň
|
||||||
|
piesok,príroda,1,piesok
|
||||||
|
pôda,príroda,2,poda|pôda
|
||||||
|
oheň,príroda,2,ohen|oheň
|
||||||
|
plameň,príroda,3,plamen|plameň
|
||||||
|
vlna,príroda,2,vlna
|
||||||
|
hmla,príroda,2,hmla
|
||||||
|
ulica,mesto,1,ulica
|
||||||
|
námestie,mesto,2,namestie|námestie
|
||||||
|
škola,mesto,1,skola|škola
|
||||||
|
obchod,mesto,1,obchod
|
||||||
|
trh,mesto,2,trh
|
||||||
|
banka,mesto,1,banka
|
||||||
|
pošta,mesto,2,posta|pošta
|
||||||
|
nemocnica,mesto,3,nemocnica
|
||||||
|
lekáreň,mesto,3,lekaren|lekáreň
|
||||||
|
stanica,mesto,2,stanica
|
||||||
|
zastávka,mesto,2,zastavka|zastávka
|
||||||
|
autobus,mesto,1,autobus
|
||||||
|
vlak,mesto,1,vlak
|
||||||
|
taxi,mesto,2,taxi
|
||||||
|
most,mesto,1,most
|
||||||
|
park,mesto,1,park
|
||||||
|
kino,druh zábavy,1,kino
|
||||||
|
divadlo,druh zábavy,2,divadlo
|
||||||
|
reštaurácia,mesto,3,restauracia|reštaurácia
|
||||||
|
kaviareň,mesto,3,kavieren|kaviareň
|
||||||
|
hotel,mesto,2,hotel
|
||||||
|
múzeum,druh zábavy,3,muzeum|múzeum
|
||||||
|
knižnica,mesto,3,kniznica|knižnica
|
||||||
|
polícia,mesto,3,policia|polícia
|
||||||
|
hlava,telo,1,hlava
|
||||||
|
oko,telo,1,oko
|
||||||
|
ucho,telo,1,ucho
|
||||||
|
nos,telo,1,nos
|
||||||
|
ústa,telo,2,usta|ústa
|
||||||
|
zub,telo,1,zub
|
||||||
|
jazyk,telo,2,jazyk
|
||||||
|
krk,telo,1,krk
|
||||||
|
rameno,telo,2,rameno
|
||||||
|
ruka,telo,1,ruka
|
||||||
|
dlaň,telo,2,dlan|dlaň
|
||||||
|
prst,telo,1,prst
|
||||||
|
lakeť,telo,3,laket|lakeť
|
||||||
|
chrbát,telo,3,chrbat|chrbát
|
||||||
|
brucho,telo,1,brucho
|
||||||
|
noha,telo,1,noha
|
||||||
|
koleno,telo,1,koleno
|
||||||
|
členok,telo,3,clenok|členok
|
||||||
|
päta,telo,2,peta|päta
|
||||||
|
srdce,telo,2,srdce
|
||||||
|
pľúca,telo,3,pluca|pľúca
|
||||||
|
žalúdok,telo,3,zaludok|žalúdok
|
||||||
|
pečeň,telo,3,pecen|pečeň
|
||||||
|
tričko,oblečenie,2,tricko|tričko
|
||||||
|
nohavice,oblečenie,1,nohavice
|
||||||
|
sukňa,oblečenie,2,sukna|sukňa
|
||||||
|
šaty,oblečenie,1,saty|šaty
|
||||||
|
sveter,oblečenie,2,sveter
|
||||||
|
bunda,oblečenie,1,bunda
|
||||||
|
kabát,oblečenie,2,kabat|kabát
|
||||||
|
topánka,oblečenie,2,topanka|topánka
|
||||||
|
ponožka,oblečenie,2,ponozka|ponožka
|
||||||
|
čiapka,oblečenie,2,ciapka|čiapka
|
||||||
|
šál,oblečenie,2,sal|šál
|
||||||
|
rukavica,oblečenie,2,rukavica
|
||||||
|
opasok,oblečenie,2,opasok
|
||||||
|
Loading…
Reference in New Issue
Block a user