All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.vsct.tock.bot.definition.BotDefinition.kt Maven / Gradle / Ivy

/*
 * Copyright (C) 2017 VSCT
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.vsct.tock.bot.definition

import fr.vsct.tock.bot.connector.ConnectorType
import fr.vsct.tock.bot.definition.Intent.Companion.keyword
import fr.vsct.tock.bot.definition.Intent.Companion.unknown
import fr.vsct.tock.bot.engine.I18nTranslator
import fr.vsct.tock.bot.engine.action.Action
import fr.vsct.tock.bot.engine.action.SendSentence
import fr.vsct.tock.bot.engine.user.PlayerId
import fr.vsct.tock.nlp.api.client.model.Entity
import fr.vsct.tock.nlp.api.client.model.EntityType
import fr.vsct.tock.shared.withNamespace
import fr.vsct.tock.shared.withoutNamespace
import fr.vsct.tock.translator.I18nKeyProvider
import fr.vsct.tock.translator.I18nLabelKey
import fr.vsct.tock.translator.Translator
import fr.vsct.tock.translator.UserInterfaceType
import java.util.Locale

/**
 * The main interface used to define the behaviour of the bot.
 *
 * New bots should usually not directly extend this class, but instead extend [BotDefinitionBase].
 */
interface BotDefinition : I18nKeyProvider {

    companion object {

        /**
         * Finds an intent from an intent name and a list of [StoryDefinition].
         * Is no valid intent found, returns [unknown].
         */
        fun findIntent(stories: List, intent: String): Intent {
            return stories.flatMap { it.intents }.find { it.name == intent }
                    ?: if (intent == keyword.name) keyword else unknown
        }

        /**
         * Finds a [StoryDefinition] from a list of [StoryDefinition] and an intent name.
         * Is no valid [StoryDefinition] found, returns the [unknownStory].
         */
        fun findStoryDefinition(
            stories: List,
            intent: String?,
            unknownStory: StoryDefinition,
            keywordStory: StoryDefinition
        ): StoryDefinition {
            return if (intent == null) {
                unknownStory
            } else {
                val i = findIntent(stories, intent)
                stories.find { it.isStarterIntent(i) }
                        ?: if (intent == keyword.name) keywordStory else unknownStory
            }
        }

    }

    /**
     * The main bot id. Have to be different for each bot.
     */
    val botId: String

    /**
     * The namespace of the bot. Have to be the same namespace than the NLP models.
     */
    val namespace: String

    /**
     * The name of the main nlp model.
     */
    val nlpModelName: String

    /**
     * The list of each stories.
     */
    val stories: List

    /**
     * This is the method called by the bot after a NLP request.
     * Overrides it if you need more control on intent choice.
     */
    fun findIntentForBot(intent: String, context: IntentContext): Intent {
        return findIntent(intent)
    }

    /**
     * Finds an [Intent] from an intent name.
     */
    fun findIntent(intent: String): Intent {
        return findIntent(stories, intent)
    }

    /**
     * Finds a [StoryDefinition] from an [Intent].
     */
    fun findStoryDefinition(intent: IntentAware?): StoryDefinition {
        return if (intent is StoryDefinition) {
            intent
        } else {
            findStoryDefinition(intent?.wrappedIntent()?.name)
        }
    }

    /**
     * Finds a [StoryDefinition] from an intent name.
     */
    fun findStoryDefinition(intent: String?): StoryDefinition {
        return findStoryDefinition(stories, intent, unknownStory, keywordStory)
    }

    /**
     * The unknown story. Used where no valid intent is found.
     */
    val unknownStory: StoryDefinition

    /**
     * To handle keywords - used to bypass nlp.
     */
    val keywordStory: StoryDefinition

    /**
     * The hello story. Used for first interaction with no other input.
     */
    val helloStory: StoryDefinition?

    /**
     * The goodbye story. Used when closing the conversation.
     */
    val goodbyeStory: StoryDefinition?

    /**
     * The no input story. When user does nothing!
     */
    val noInputStory: StoryDefinition?

    /**
     * The story that handles [fr.vsct.tock.bot.engine.action.SendLocation] action. If it's null, current intent is used.
     */
    val userLocationStory: StoryDefinition?

    /**
     * The story that handles [fr.vsct.tock.bot.engine.action.SendAttachment] action. If it's null, current intent is used.
     */
    val handleAttachmentStory: StoryDefinition?

    /**
     * To handle custom events.
     */
    val eventListener: EventListener

    /**
     * Called when error occurs. By default send "technical error".
     */
    fun errorAction(playerId: PlayerId, applicationId: String, recipientId: PlayerId): Action {
        return SendSentence(
            playerId,
            applicationId,
            recipientId,
            "Technical error :( sorry!"
        )
    }

    /**
     * To manage deactivation.
     */
    val botDisabledStory: StoryDefinition?


    /**
     * Is this intent disable the bot?
     */
    fun isBotDisabledIntent(intent: Intent?): Boolean =
        intent != null && botDisabledStory?.isStarterIntent(intent) ?: false

    /**
     * To manage reactivation.
     */
    val botEnabledStory: StoryDefinition?

    /**
     * Is this intent is reactivating the bot?
     */
    fun isBotEnabledIntent(intent: Intent?): Boolean =
        intent != null && botEnabledStory?.isStarterIntent(intent) ?: false

    /**
     * Returns a [TestBehaviour]. Used in Integration Tests.
     */
    val testBehaviour: TestBehaviour get() = TestBehaviourBase()

    override fun i18nKeyFromLabel(defaultLabel: CharSequence, args: List): I18nLabelKey {
        val prefix = javaClass.kotlin.simpleName?.replace("Definition", "") ?: ""
        return i18nKey(
            "${prefix}_${Translator.getKeyFromDefaultLabel(defaultLabel)}",
            namespace,
            prefix,
            defaultLabel,
            args
        )
    }

    /**
     * Returns the entity with the specified name and optional role.
     */
    fun entity(name: String, role: String? = null): Entity =
        Entity(
            EntityType(name.withNamespace(namespace)),
            role ?: name.withoutNamespace(namespace)
        )

    /**
     * Returns an [I18nTranslator] for the specified [userLocale] and [connectorType].
     */
    fun i18nTranslator(
        userLocale: Locale,
        connectorType: ConnectorType,
        userInterfaceType: UserInterfaceType = connectorType.userInterfaceType
    ): I18nTranslator =
        object : I18nTranslator {
            override val userLocale: Locale
                get() = userLocale
            override val userInterfaceType: UserInterfaceType
                get() = userInterfaceType
            override val targetConnectorType: ConnectorType
                get() = connectorType

            override fun i18nKeyFromLabel(defaultLabel: CharSequence, args: List): I18nLabelKey {
                return [email protected](defaultLabel, args)
            }
        }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy