{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-| Module : Dialogflow.V2.Util Description : Dialogflow types for response messages. Copyright : (c) Mauricio Fierro, 2019 License : BSD3-Clause Maintainer : Mauricio Fierro This module contains types for Dialogflow messages to be used in a fulfillment webhook response. See the Dialogflow . -} module Dialogflow.V2.Fulfillment.Message ( CardButton(..) , BasicCardContent(..) , BasicCardButton(..) , Item(..) , OpenUriAction(..) , SpeechText(..) , SimpleResponse(..) , Suggestion(..) , SelectItemInfo(..) , MsgType(..) , Msg( Text , Image , QuickReplies , Card , SimpleResponses , BasicCard , Suggestions , LinkOutSuggestion , ListSelect , CarouselSelect ) , Message(..) ) where import Data.Aeson ( FromJSON , parseJSON , ToJSON , toJSON , Value(..) , withObject , (.:) , (.:!) , (.=)) import Data.Foldable (asum) import qualified Data.HashMap.Strict as HM import Dialogflow.Util -- | Button for a 'Card' message data CardButton = CardButton { cbText :: Maybe String -- ^ The text to show on the button. , cbPostback :: Maybe String -- ^ The text to send to the Dialogflow API or URI to open. } deriving (Eq, Show) instance FromJSON CardButton where parseJSON = withObject "cardButton" $ \cb -> do cbText <- cb .:! "text" cbPostback <- cb .:! "postback" return CardButton{..} instance ToJSON CardButton where toJSON CardButton{..} = noNullObjects [ "text" .= cbText , "postback" .= cbPostback ] -- | The 'BasicCard' message can have either an 'Image' or -- formatted text as content. data BasicCardContent = BasicCardImage (Msg 'MsgImage) | BasicCardFormattedText String deriving (Eq, Show) instance FromJSON BasicCardContent where parseJSON = withObject "Image or formatted text" $ \bcc -> asum [ BasicCardImage <$> bcc .: "image" , BasicCardFormattedText <$> bcc .: "formatted_text" ] instance ToJSON BasicCardContent where toJSON = \case BasicCardImage image -> noNullObjects [ "image" .= image ] BasicCardFormattedText formattedText -> noNullObjects [ "formatted_text" .= formattedText ] -- | A 'SimpleResponse' can have text-to-speech in plain text -- or SSML format. data SpeechText = TextToSpeech String -- ^ The plain text of the speech output | SSML String -- ^ Structured spoken response to the user in SSML format deriving (Eq, Show) instance FromJSON SpeechText where parseJSON = withObject "textToSpeech or SSML" $ \st -> asum [ TextToSpeech <$> st .: "textToSpeech" , SSML <$> st .: "ssml" ] instance ToJSON SpeechText where toJSON = \case TextToSpeech textToSpeech -> noNullObjects ["textToSpeech" .= textToSpeech] SSML ssml -> noNullObjects ["ssml" .= ssml] -- | A simple response message containing speech or text data SimpleResponse = SimpleResponse { simpleResponseText :: SpeechText -- ^ The speech text , displayText :: Maybe String -- ^ The text to display } deriving (Eq, Show) instance FromJSON SimpleResponse where parseJSON = withObject "simpleResponse" $ \sr -> do simpleResponseText <- parseJSON (Object sr) displayText <- sr .:! "displayText" return SimpleResponse{..} instance ToJSON SimpleResponse where toJSON SimpleResponse{..} = Object $ toObject simpleResponseText <> HM.fromList ["displayText" .= displayText ] -- | An action to open the given URI. Used in 'BasicCardButton's. newtype OpenUriAction = OpenUriAction { unOpenUriAction :: String -- ^ The HTTP or HTTPS scheme URI } deriving (Eq, Show) instance ToJSON OpenUriAction where toJSON oua = noNullObjects [ "uri" .= unOpenUriAction oua ] instance FromJSON OpenUriAction where parseJSON = withObject "openUriAction" $ \oua -> do uri <- oua .: "uri" return $ OpenUriAction uri -- | Buttons for 'BasicCard's. data BasicCardButton = BasicCardButton { bcbTitle :: String -- ^ The title of the button , bcbOpenUriAction :: OpenUriAction -- ^ Action to take when a user taps on the button } deriving (Eq, Show) instance FromJSON BasicCardButton where parseJSON = withObject "basicCardButton" $ \bcb -> do bcbTitle <- bcb .: "title" bcbOpenUriAction <- bcb .: "open_uri_action" return BasicCardButton{..} instance ToJSON BasicCardButton where toJSON BasicCardButton{..} = noNullObjects [ "title" .= bcbTitle , "open_uri_action" .= bcbOpenUriAction ] -- | Suggestion chips. newtype Suggestion = Suggestion { unSuggestionTitle :: String -- ^ The text shown in the suggestion chip } deriving (Eq, Show) instance FromJSON Suggestion where parseJSON = withObject "suggestion" $ \s -> do unSuggestionTitle <- s .: "title" return $ Suggestion unSuggestionTitle instance ToJSON Suggestion where toJSON s = noNullObjects [ "title" .= unSuggestionTitle s ] -- | Additional information about an 'Item' for when it is triggered in a dialog. data SelectItemInfo = SelectItemInfo { siiKey :: String -- ^ A unique key that will be sent back to the agent if this response is given. , siiSynonyms :: Maybe [String] -- ^ A list of synonyms that can also be used to trigger this item in dialog. } deriving (Eq, Show) instance FromJSON SelectItemInfo where parseJSON = withObject "selectedItemInfo" $ \sii -> do siiKey <- sii .: "key" siiSynonyms <- sii .: "synonyms" return SelectItemInfo{..} instance ToJSON SelectItemInfo where toJSON SelectItemInfo{..} = noNullObjects [ "key" .= siiKey , "synonyms" .= siiSynonyms ] -- | The possible types of 'Message'. data MsgType = MsgText | MsgImage | MsgQuickReplies | MsgCard | MsgSimpleResponses | MsgBasicCard | MsgSuggestions | MsgLinkOutSuggestion | MsgListSelect | MsgCarouselSelect -- | The messages to be included in the Response. data Msg t where -- | The text response message. Text :: Maybe [String] -- ^ The collection of the agent's responses -> Msg 'MsgText -- | The image response message. Image :: String -- ^ The public URI to an image file -> Maybe String -- ^ A text description of the image to be used for accessibility -> Msg 'MsgImage -- | The quick replies response message. QuickReplies :: Maybe String -- ^ The title of the collection of quick replies -> [String] -- ^ The collection of quick replies -> Msg 'MsgQuickReplies -- | The card response. Card :: Maybe String -- ^ The title of the card -> Maybe String -- ^ The subtitle of the card -> Maybe String -- ^ The public URI to an image file for the card -> Maybe [CardButton] -- ^ The collection of card buttons -> Msg 'MsgCard -- | The collection of 'SimpleResponse' candidates. SimpleResponses :: [SimpleResponse] -- ^ The list of simple responses -> Msg 'MsgSimpleResponses -- TODO: Check if the formattedText and image fields are mutually exclusive -- | The basic card message. Useful for displaying information. BasicCard :: Maybe String -- ^ The title of the card -> Maybe String -- ^ The subtitle of the card -> BasicCardContent -- ^ The body text or image of the card -> Maybe [BasicCardButton] -- ^ The collection of card buttons -> Msg 'MsgBasicCard -- | The collection of 'Suggestion'. Suggestions :: [Suggestion] -- ^ The list of suggested replies -> Msg 'MsgSuggestions -- | The suggestion chip message that allows the user to jump -- out to the app or the website associated with this agent. LinkOutSuggestion :: String -- ^ The name of the app or site this chip is linking to -> String -- ^ The URI of the app or site to open when the user taps the suggestion chip -> Msg 'MsgLinkOutSuggestion -- | The card for presenting a list of options to select from. ListSelect :: Maybe String -- ^ The overall title of the list -> [Item] -- ^ List items -> Msg 'MsgListSelect -- | The card for representing a carousel of options to select from. CarouselSelect :: [Item] -- ^ Carousel items -> Msg 'MsgCarouselSelect deriving instance Show (Msg t) deriving instance Eq (Msg t) instance FromJSON (Msg 'MsgImage) where parseJSON = withObject "image" $ \i -> do uri <- i .: "image_uri" allyText <- i .:! "accessibility_text" return (Image uri allyText) instance FromJSON (Msg 'MsgText) where parseJSON = withObject "text" $ \t -> do text <- t .: "text" return $ Text text instance FromJSON (Msg 'MsgQuickReplies) where parseJSON = withObject "quickReplies" $ \qr -> do title <- qr .:! "title" replies <- qr .: "quick_replies" return $ QuickReplies title replies instance FromJSON (Msg 'MsgCard) where parseJSON = withObject "card" $ \card -> do c <- card .: "card" mbTitle <- c .:! "title" mbSubtitle <- c .:! "subtitle" mbUri <- c .:! "image_uri" cardButtons <- c .:! "buttons" return $ Card mbTitle mbSubtitle mbUri cardButtons instance FromJSON (Msg 'MsgSimpleResponses) where parseJSON = withObject "simpleResponses" $ \sr -> do srs <- sr .: "simpleResponses" responses <- srs .: "simpleResponses" return $ SimpleResponses responses instance FromJSON (Msg 'MsgBasicCard) where parseJSON = withObject "basicCard" $ \bc -> do mbTitle <- bc .:! "title" mbSubtitle <- bc .:! "subtitle" content <- parseJSON (Object bc) buttons <- bc .:! "buttons" return $ BasicCard mbTitle mbSubtitle content buttons instance FromJSON (Msg 'MsgSuggestions) where parseJSON = withObject "suggestions" $ \sgs -> do suggestions <- sgs .: "suggestions" return $ Suggestions suggestions instance FromJSON (Msg 'MsgLinkOutSuggestion) where parseJSON = withObject "linkOutSuggestion" $ \los -> do uri <- los .: "uri" destinationName <- los .: "destination_name" return $ LinkOutSuggestion destinationName uri instance FromJSON (Msg 'MsgListSelect) where parseJSON = withObject "listSelect" $ \ls -> do title <- ls .:! "title" items <- ls .: "items" return $ ListSelect title items instance FromJSON (Msg 'MsgCarouselSelect) where parseJSON = withObject "carouselSelect" $ \cs -> do items <- cs .: "items" return $ CarouselSelect items instance ToJSON (Msg t) where toJSON (Text mbText) = noNullObjects [ "text" .= mbText ] toJSON (Image uri accesibilityText) = noNullObjects [ "image_uri" .= uri , "accessibility_text" .= accesibilityText ] toJSON (QuickReplies mbTitle quickReplies) = noNullObjects [ "title" .= mbTitle , "quick_replies" .= quickReplies ] toJSON (Card title subtitle imageUri buttons) = noNullObjects [ "card" .= noNullObjects ["title" .= title , "subtitle" .= subtitle , "image_uri" .= imageUri , "buttons" .= buttons ] ] toJSON (SimpleResponses simpleResponses) = noNullObjects [ "simpleResponses" .= noNullObjects ["simpleResponses" .= simpleResponses ] ] toJSON (BasicCard mbTitle mbSubtitle content buttons) = Object $ HM.fromList [ "title" .= mbTitle , "subtitle" .= mbSubtitle , "buttons" .= buttons ] <> toObject content toJSON (Suggestions xs) = noNullObjects [ "suggestions" .= xs ] toJSON (LinkOutSuggestion name uri) = noNullObjects [ "destination_name" .= name, "uri" .= uri ] toJSON (ListSelect mbTitle items) = noNullObjects [ "title" .= mbTitle , "items" .= items ] toJSON (CarouselSelect items) = noNullObjects [ "items" .= items ] -- | This type is used to wrap the messages under one type. data Message where Message :: (Show (Msg t)) => Msg t -> Message instance Show Message where show (Message o) = show o instance ToJSON Message where toJSON (Message bc@BasicCard{}) = noNullObjects [ "basicCard" .= toJSON bc ] toJSON (Message o) = toJSON o instance Eq Message where (==) (Message x@Text{}) (Message y@Text{}) = x == y (==) (Message x@Image{}) (Message y@Image{}) = x == y (==) (Message x@QuickReplies{}) (Message y@QuickReplies{}) = x == y (==) (Message x@Card{}) (Message y@Card{}) = x == y (==) (Message x@SimpleResponses{}) (Message y@SimpleResponses{}) = x == y (==) (Message x@BasicCard{}) (Message y@BasicCard{}) = x == y (==) (Message x@Suggestions{}) (Message y@Suggestions{}) = x == y (==) (Message x@LinkOutSuggestion{}) (Message y@LinkOutSuggestion{}) = x == y (==) (Message x@ListSelect{}) (Message y@ListSelect{}) = x == y (==) (Message x@CarouselSelect{}) (Message y@CarouselSelect{}) = x == y (==) _ _ = False -- | An item in 'ListSelect' and 'CarouselSelect'. data Item = Item { iInfo :: SelectItemInfo -- ^ Additional information about this option , iTitle :: String -- ^ The title of the list item , iDescription :: Maybe String -- ^ The main text describing the item , iImage :: Msg 'MsgImage -- ^ The image to display } deriving (Eq, Show) instance FromJSON Item where parseJSON = withObject "Item" $ \i -> do iInfo <- i .: "info" iTitle <- i .: "title" iDescription <- i .:! "description" iImage <- i .: "image" return Item{..} instance ToJSON Item where toJSON Item{..} = noNullObjects [ "info" .= iInfo , "title" .= iTitle , "description" .= iDescription , "image" .= iImage ]