The response from the intent can hold additional meta data such as action buttons, images and field data making messages more interactive.

An intent can return two action attachments “Yes” and “No” which could be passed to Facebook to show two buttons with the message.

request.attachment('action', 'Yes');
request.attachment('action', 'No');
Cheese
Do you like cheese?
Yes No

Supported attachment types are currently:

  • action - Button options to show the user
  • image - Send image URL’s to be displayed
  • field - Additional small information, e.g. citation
  • reply - Expecting a reply
  • voice - Alternative for voice instead of using standard message text

Enabling attachments

Attachments are found in /src/Attachment/

For an attachment to work they must be enabled in your config.js file and the server must be restarted.

config.attachments = [
  'Sys.Attachment.Image',
  'Sys.Attachment.Action',
  'Sys.Attachment.Field'
];

Client support

The clients must support these attachment types to send to the end platform. Each platform handle these attachments differently and can have restrictions. For example Facebook support actions and buttons but have a limit of three buttons in total.

GI’s attachments were loosely based on the Slack API.

Action buttons

Show option buttons to the user.

request.attachment('action', 'Rock');
request.attachment('action', 'Paper');
request.attachment('action', 'Scissors');
Play rock paper scissors
Rock, paper or scissors?
Rock Paper Scissors

After one of these buttons is pressed it will send the text back to the GI client, for example “yes”.

But without using expect and a parameter the next input of “yes” or “no” won’t be recognised.

Images

Used for showing buttons to a user.

request.attachment('image','https://picsum.photos/300/300/?random');
Random picture

Voice

If you want the client to handle a voice message differently you can use the voice attachment.

module.exports = class RandomPictureIntent extends Intent {

  setup() {
    this.train([
      'random picture'
    ]);
  }

  response(request) {
    request.attachment('image','https://picsum.photos/300/300/?random');
    request.attachment('voice','I can\'t show you a picture');
    return true;
  }

}

Trapping example

This example uses attachment of buttons, parameters to fetch the confirmation and expecting to wait for the reply.

The Apptem Confirm entity has a small dictionary of common confirmations for yes and no.

module.exports = class AskMeAgainIntent extends Intent {

  setup() {
    this.train(['ask me again'], { classifier:'strict' });

    this.parameter('ask_again', {
      name: 'Ask again',
      entity: 'App.Common.Entity.Confirm'
    });
  }

  response(request) {
    request.attachment('action','Yes');
    request.attachment('action','No');

    request.expect({
      action: 'chosen',
      force: true
    });

    return 'Ask me again?';
  }

  chosen(request) {
    var choice = request.parameters.value('ask_again');
    if(choice == 'yes') {
      return request.redirect('App.Example.Intent.AskMeAgain');
    }
    return 'OK that is enough';
  }

}
Ask me again
Yes No
Yes
Yes No
Yup!
Yes No
No
OK that is enough

Creating an attachment

Within your skill directory create a new directory called Attachment and create a file prepending the name of the attachment.

This following example is stored in app/Skill/Example/Attachment/navigation.js.

module.exports = class NavigationAttachment extends Attachment {

  constructor(app) {
    super(app);

    //Can't have more than one of these attachments
    this.multiple = false;
  }

  build(data) {
    return {
      name: data.name,
      url: data.url
    };
  }

}

Enable the new attachment in your app config using the identifier name.

config.attachments = [
  'App.Example.Attachment.Navigation'
];

Then your intents from any skill can use this attachment.

module.exports = class DocumentationIntent extends Intent {

  setup() {
    this.train([
      '@App.Example.Entity.Documentation',
      new RegExp(/^go to/,'g')
    ]);

    this.parameter('page', {
      name: "Page",
      entity: "App.Example.Entity.Documentation"
    });
  }

  response(request) {
    if(!request.parameters.has('page')) {
      return 'I\'m not sure of that page in the documentation';
    }

    let label = request.parameters.get('page.data.label');
    let url = request.parameters.get('page.data.url');

    request.attachment('navigation', {
      name: label,
      url: url
    });

    return 'Taking you to '+label;
  }

}

This attachment will then be included in the response that your client can handle.

{
  "attachments": {
    "navigation": {
      "name": "Intent attachments",
      "url": "/docs/intents/attachments"
    }
  }
}
go to attachments
Taking you to Intent attachments