Skip to content

Commit

Permalink
Cards, types, other util & fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
valgaze committed Oct 13, 2021
1 parent 872f4bd commit 6c7c074
Show file tree
Hide file tree
Showing 14 changed files with 653 additions and 174 deletions.
50 changes: 9 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Speedybot is a "toolkit" to take you from zero to a non-trivial bot as quickly a

Speedybot instruments on top of the incredibly useful **[webex-node-bot-framework](https://github.com/WebexSamples/webex-node-bot-framework)** and steps through the fastest path to a working bot and provides some convenience features

Even if you don't use all of speedybot's features, if nothing else there are several helper utillties that are useful for crafting a rich conversation agent-- more details **[here](https://github.com/valgaze/speedybot/blob/master/docs/util.md)** Speedybot has one required dependency

## Adding a new chat handler

Expand Down Expand Up @@ -73,6 +74,7 @@ There are a few "special" keywords you can use to "listen" to special events:
ex. Tell the bot "sendcard" to get a card, type into the card & tap submit, catch submission using *<@submit>* and echo back to user

```ts
import { Card } from 'speedybot'
export default [{
keyword: '<@submit>',
handler(bot, trigger) {
Expand All @@ -84,48 +86,14 @@ export default [{
keyword: 'sendcard',
handler(bot, trigger) {
bot.say('One card on the way...')

// Adapative Card: https://developer.webex.com/docs/api/guides/cards
const cardPayload = {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "System is 👍"
}, {
"type": "RichTextBlock",
"inlines": [{
"type": "TextRun",
"text": "If you see this card, everything is working"
}]
}, {
"type": "Image",
"url": "https://i.imgur.com/SW78JRd.jpg",
"horizontalAlignment": "Center",
"size": "large"
}, {
"type": "Input.Text",
"id": "inputData",
"placeholder": "What's on your mind?"
}],
"actions": [{
"type": "Action.OpenUrl",
"title": "Take a moment to celebrate",
"url": "https://www.youtube.com/watch?v=3GwjfUFyY6M",
"style": "positive"
}, {
"type": "Action.Submit",
"title": "Submit",
"data": {
"cardType": "inputForm"
}
}]
}

bot.sendCard(cardPayload, 'Your client does not currently support Adaptive Cards')
const myCard = new SpeedyCard().setTitle('System is 👍')
.setSubtitle('If you see this card, everything is working')
.setImage('https://i.imgur.com/SW78JRd.jpg')
.setInput(`What's on your mind?`)
.setUrl('https://www.youtube.com/watch?v=3GwjfUFyY6M', 'Take a moment to celebrate')
.setTable([[`Bot's Date`, new Date().toDateString()], ["Bot's Uptime", `${String(process.uptime())}s`]])
bot.sendCard(myCard.render(), 'Your client does not currently support Adaptive Cards')
},
helpText: 'Sends an Adaptive Card with an input field to the user'
}
Expand Down
23 changes: 23 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## Speedybot

- **[Various Helpers (cards, add variation, templating, storage, etc)](./util.md)**

- **[Common Patterns & How-To](./how-to.md)**

### Bot Token

- Create new bot: https://developer.webex.com/my-apps/new/bot

- Get an existing bot's token (tap "regenerate"): https://developer.webex.com/my-apps

### Useful reading

- https://developer.webex.com/blog/from-zero-to-webex-teams-chatbot-in-15-minutes

- https://developer.webex.com/blog/introducing-the-webex-teams-bot-framework-for-node-js

- https://github.com/WebexSamples/webex-card-school/blob/master/README.md

- https://developer.webex.com/blog/five-tips-for-well-behaved-webex-bots

- https://github.com/WebexSamples/webex-node-bot-framework/blob/master/docs/buttons-and-cards-example.md
10 changes: 7 additions & 3 deletions docs/how-to.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ There are a few "special" keyword words you can use which have a special meaning


- *<@webhook>*: Put your webhook handlers alongside your handlers, see the **["Handling Webhooks"](#handling-webhooks)** section below for an example or video instruction here: https://share.descript.com/view/bnyupJvNJcx
ex.




ex. Kitchen sink handler list

```ts
export default handlers = [
Expand Down Expand Up @@ -319,8 +323,8 @@ Ex. Process incoming webhooks that post to your agent:
],
}

this.send({toPersonEmail: 'valgaze+botexperiments@gmail.com', text: msg})
this.sendCardToPerson('valgaze+botexperiments@gmail.com', cardJson)
this.send({toPersonEmail: 'joe@joe.com', text: msg})
this.sendCardToPerson('joe@joe.com', cardJson)

res.send('Thanks') // optional, in case server needs acknowledgement
}
Expand Down
156 changes: 156 additions & 0 deletions docs/util.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
## Utilities and Helper Functions

## SpeedyCard

- Getting started with AdaptiveCards (https://developer.webex.com/docs/api/guides/cards) can be a bit cumbersome and error-prone

- SpeedyCard is a limited subset of AdaptiveCards with basic features with a focus on user interaction & simplicity (title, text, input box, menu-select, no "collapsable" sections, etc)

- Inspired a bit by SwiftUI: https://developer.apple.com/xcode/swiftui/

- Make sure you call ```.render()``` and use the ```.sendCard``` method

<details><summary>Before/After</summary>

## Before

```ts
const cardJson = {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [{
"type": "TextBlock",
"text": "System is 👍",
"weight": "Bolder",
"size": "Large",
"wrap": true
}, {
"type": "TextBlock",
"text": "If you see this card, everything is working",
"size": "Small",
"isSubtle": true,
"wrap": true,
"weight": "Lighter"
}, {
"type": "Image",
"url": "https://i.imgur.com/SW78JRd.jpg",
"horizontalAlignment": "Center",
"size": "Large"
}, {
"type": "Input.Text",
"placeholder": "What's on your mind?",
"id": "inputData"
}],
"actions": [{
"type": "Action.Submit",
"title": "Submit",
"data": {
"cardType": "inputForm"
}
}, {
"type": "Action.OpenUrl",
"title": "Take a moment to celebrate",
"url": "https://www.youtube.com/watch?v=3GwjfUFyY6M"
}]
}

bot.sendCard(cardJson, 'Your client does not currently support Adaptive Cards')
```

## After

```ts
const cardJson = new SpeedyCard().setTitle('System is 👍', {st})
.setSubtitle('If you see this card, everything is working')
.setImage('https://i.imgur.com/SW78JRd.jpg')
.setInput(`What's on your mind?`)
.setUrl('https://www.youtube.com/watch?v=3GwjfUFyY6M', 'Take a moment to celebrate')

bot.sendCard(cardJson.render(), 'Your client does not currently support Adaptive Cards')

```

</details>


```ts
import { SpeedyCard } from 'speedybot'
export const handlers = [{
keyword: ['go!'],
handler(bot, trigger) {
// Adapative Card: https://developer.webex.com/docs/api/guides/cards
const myCard = new SpeedyCard().setTitle('System is 👍', {st})
.setSubtitle('If you see this card, everything is working')
.setImage('https://i.imgur.com/SW78JRd.jpg')
.setInput(`What's on your mind?`)
.setUrl('https://www.youtube.com/watch?v=3GwjfUFyY6M', 'Take a moment to celebrate')
.setChoices(['Abraham Lincoln','Adlai Stevenson','Connie Rice', 'Monty'], {id:})
.setTable([[`Bot's Time`, new Date().toTimeString()], ['Bot running duration', process.uptime()]])
bot.sendCard(myCard.render(), 'Your client does not currently support Adaptive Cards')

}
helpText: 'A demo handler invoked when the user types go!'
},
{
keyword: '<@submit>',
handler(bot, trigger) {
bot.say(`Submission received! You sent us ${JSON.stringify(trigger.attachmentAction.inputs)}`)
},
helpText: 'Special handler that fires when data is submitted'
}

```
## Storage
- Lightweight helper for storage, scoped to user
- Get operations don't error if none found, return null instead
```ts
import { Storage } from 'speedybot'

const handler = {
keyword: ['go!'],
async handler(bot, trigger) {
const val = await Storage.get('secret_code')
if (val) {
bot.say(`Your stored value is ${val}`)
} else {
const secretCode = Math.random().toString(36).slice(2)
bot.say(`No stored value detected, saving '${secretCode}' now...`)
await Storage.get('secret_code', secretCode)
}
}
helpText: 'A demo handler invoked when the user types go!'
}
```
## fillTemplate & pickRandom
- Variation in responses is a low-investment, high-payoff way to make conversation agents more appealing and less robotic
- pickRandom does exactly what it sounds like-- randomly return an item from a list
- fillTemplate will randomly pick an item from a list and replace ```$[template_name]``` with a specified value
```ts
import { fillTemplate } from 'speedybot'

const handler = {
keyword: ['go!'],
async handler(bot, trigger) {
const phrases = ["Hey $[name], how's it going? Here is one $[flavor] ice cream", "Hiya $[name], here's your $[flavor]", "Here's one $[flavor] for ya, $[name]"]
const myTemplate = {
name: trigger.person.displayName,
flavor: 'mint'
}

bot.say(fillTemplate(phrases, myTemplate))

}
helpText: 'A demo handler invoked when the user types go!'
}
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "1.0.6",
"description": "Speedy & easy way to rapidly iterate with conversation bots",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"scripts": {
"test": "npm run build && node_modules/.bin/tape dist/test/*.test.js",
"patch": "npx np patch",
Expand Down
10 changes: 7 additions & 3 deletions settings/namegame.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// See here: https://www.youtube.com/watch?v=NeF7jqf0GU4
import { pickRandom, easyCard, Trigger, BotInst } from './../src'
import { pickRandom, Trigger, BotInst, SpeedyCard} from './../src'
export default {
keyword: ['namegame', 'namegame:start'],
handler(bot: BotInst, trigger: Trigger) {
Expand All @@ -16,14 +16,18 @@ export default {
ex. namegame Lincoln
🎶🎶🎸🎶🎶
`
return bot.sendCard(easyCard({ title: 'The Name Game by Shirley Ellis', text, url: 'https://www.youtube.com/watch?v=NeF7jqf0GU4', buttonLabel: 'Boogie!', image: 'https://i3.ytimg.com/vi/NeF7jqf0GU4/hqdefault.jpg' }), 'The Name Game by Shirley Ellis: https://www.youtube.com/watch?v=NeF7jqf0GU4')

const myCard = new SpeedyCard().setTitle('The Name Game by Shirley Ellis').setSubtitle(text).setUrl('https://www.youtube.com/watch?v=NeF7jqf0GU4').setImage('https://i3.ytimg.com/vi/NeF7jqf0GU4/hqdefault.jpg')
return bot.sendCard(myCard.render(), 'It appears your client does not support adaptive cards')
}

const firstName = name ? name : trigger.person.firstName;
const res = lyricsGenerator(firstName)
const warmup = ['Alright,', 'Here we go', 'Ready?', 'Deep breath...']
const output = `${pickRandom(warmup)} ${res}`
bot.sendCard(easyCard({ title: 'The Name Game by Shirley Ellis', text: output, url: 'https://www.youtube.com/watch?v=NeF7jqf0GU4', buttonLabel: 'Go!', image: 'https://i3.ytimg.com/vi/NeF7jqf0GU4/hqdefault.jpg' }), 'The Name Game by Shirley Ellis: https://www.youtube.com/watch?v=NeF7jqf0GU4')

const myCard = new SpeedyCard().setTitle('The Name Game by Shirley Ellis').setSubtitle(output).setUrl('https://www.youtube.com/watch?v=NeF7jqf0GU4').setImage('https://i3.ytimg.com/vi/NeF7jqf0GU4/hqdefault.jpg')
bot.sendCard(myCard.render(), 'The Name Game by Shirley Ellis: https://www.youtube.com/watch?v=NeF7jqf0GU4')
if (!name) {
bot.say('Psst, try adding a name, like this: @botname namegame Shirley')
}
Expand Down
Loading

0 comments on commit 6c7c074

Please sign in to comment.