Skip to content

Commit

Permalink
Add cards bot sample (#68)
Browse files Browse the repository at this point in the history
* add cards bot sample

* remove action card

---------

Co-authored-by: Rido <[email protected]>
  • Loading branch information
JhontSouth and rido-min committed Feb 24, 2025
1 parent fc215ba commit e44b3ba
Show file tree
Hide file tree
Showing 9 changed files with 637 additions and 0 deletions.
3 changes: 3 additions & 0 deletions samples/basic/cards/nodejs/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
registry=https://registry.npmjs.org/
@microsoft:registry=https://pkgs.dev.azure.com/ConversationalAI/BotFramework/_packaging/SDK/npm/registry/
package-lock=false
107 changes: 107 additions & 0 deletions samples/basic/cards/nodejs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Cards-bot

This is a sample of a simple Agent that is hosted on an Node.js web service. This Agent is configured to show how to create a bot that uses rich cards to enhance your bot design.

## Prerequisites

- [Node.js](https://nodejs.org) version 20 or higher

```bash
# determine node version
node --version
```

## Running this sample

1. Open this folder from your IDE or Terminal of preference
1. Install dependencies

```bash
npm install
```

### Run in localhost, anonymous mode

1. Create the `.env` file (or rename env.TEMPLATE)

```bash
cp env.TEMPLATE .env
```

1. Start the application

```bash
npm start
```

At this point you should see the message

```text
Server listening to port 3978 for appId debug undefined
```

The bot is ready to accept messages.

### Interact with the bot from the Teams App Test Tool

To interact with the bot you need a chat client, during the install phase we have acquired the `teams-test-app-tool` than can be used to interact with your bot running in `localhost:3978`

1. Start the test tool with

```bash
npm run test-tool
```

The tool will open a web browser showing the Teams App Test Tool, ready to send messages to your bot.

Alternatively you can run the next command to start the bot and the test tool with a single command (make sure you stop the bot started previously):

```bash
npm test
```

Refresh the browser to start a new conversation with the Cards bot.

You should see a message with the list of available cards in Agents:
- Adaptive Card
- Animation Card
- Audio Card
- Hero Card
- Receipt Card
- O365 Connector Card
- Thumbnail Card
- Video Card

### Interact with the bot from WebChat using Azure Bot Service

1. [Create an Azure Bot](https://aka.ms/AgentsSDK-CreateBot)
- Record the Application ID, the Tenant ID, and the Client Secret for use below

2. Configuring the token connection in the Agent settings
1. Open the `env.TEMPLATE` file in the root of the sample project, rename it to `.env` and configure the following values:
1. Set the **clientId** to the AppId of the bot identity.
2. Set the **clientSecret** to the Secret that was created for your identity.
3. Set the **tenantId** to the Tenant Id where your application is registered.

3. Install the tool [dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows)
4. Run `dev tunnels`. See [Create and host a dev tunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows) and host the tunnel with anonymous user access command as shown below:

```bash
devtunnel host -p 3978 --allow-anonymous
```

5. Take note of the url shown after `Connect via browser:`

6. On the Azure Bot, select **Settings**, then **Configuration**, and update the **Messaging endpoint** to `{tunnel-url}/api/messages`

7. Start the Agent using `npm start`

8. Select **Test in WebChat** on the Azure portal.

### Deploy to Azure

[TBD]

## Further reading

To learn more about building Bots and Agents, see our [Microsoft 365 Agents SDK](https://github.com/microsoft/agents) repo.
4 changes: 4 additions & 0 deletions samples/basic/cards/nodejs/env.TEMPLATE
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# rename to .env
tenantId=
clientId=
clientSecret=
28 changes: 28 additions & 0 deletions samples/basic/cards/nodejs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "cards-bot",
"version": "1.0.0",
"private": true,
"description": "Agents cards bot sample",
"author": "Microsoft",
"license": "MIT",
"main": "./lib/index.js",
"scripts": {
"build": "tsc --build",
"prestart": "npm run build",
"start": "node --env-file .env ./dist/index.js",
"test-tool": "teamsapptester start",
"test": "npm-run-all -p -r start test-tool"
},
"dependencies": {
"@microsoft/agents-bot-hosting": "0.1.20",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0"
},
"devDependencies": {
"@microsoft/teams-app-test-tool": "^0.2.6",
"@types/node": "^22.13.4",
"npm-run-all": "^4.1.5",
"typescript": "^5.7.2"
},
"keywords": []
}
68 changes: 68 additions & 0 deletions samples/basic/cards/nodejs/src/bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { ActivityHandler, Activity, ActivityTypes } from '@microsoft/agents-bot-hosting'
import { CardMessages } from './cardMessages'
import AdaptiveCard from './resources/adaptiveCard.json'

export class CardFactoryBot extends ActivityHandler {
constructor() {
super()

this.onMembersAdded(async (context, next) => {
const membersAdded = context.activity.membersAdded
for (let cnt = 0; cnt < membersAdded!.length; cnt++) {
if ((context.activity.recipient != null) && membersAdded![cnt].id !== context.activity.recipient.id) {
await CardMessages.sendIntroCard(context)

await next()
}
}
})

this.onMessage(async (context, next) => {
if (context.activity.text !== undefined) {
switch (context.activity.text.split('.')[0].toLowerCase()) {
case 'display cards options':
await CardMessages.sendIntroCard(context)
break
case '1':
await CardMessages.sendAdaptiveCard(context, AdaptiveCard)
break
case '2':
await CardMessages.sendAnimationCard(context)
break
case '3':
await CardMessages.sendAudioCard(context)
break
case '4':
await CardMessages.sendHeroCard(context)
break
case '5':
await CardMessages.sendReceiptCard(context)
break
case '6':
await CardMessages.sendThumbnailCard(context)
break
case '7':
await CardMessages.sendVideoCard(context)
break
default: {
const reply: Activity = Activity.fromObject(
{
type: ActivityTypes.Message,
text: 'Your input was not recognized, please try again.'
}
)
await context.sendActivity(reply)
await CardMessages.sendIntroCard(context)
}
}
} else {
await context.sendActivity('This sample is only for testing Cards using CardFactory methods. Please refer to other samples to test out more functionalities.')
}

await next()
})
}
}
171 changes: 171 additions & 0 deletions samples/basic/cards/nodejs/src/cardMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { CardFactory, TurnContext, ActionTypes, Activity, ActivityTypes, Attachment } from '@microsoft/agents-bot-hosting'

export class CardMessages {
static async sendIntroCard(context: TurnContext): Promise<void> {
// Note that some channels require different values to be used in order to get buttons to display text.
// In this code the web chat is accounted for with the 'title' parameter, but in other channels you may
// need to provide a value for other parameters like 'text' or 'displayText'.
const buttons = [
{ type: ActionTypes.ImBack, title: '1. Adaptive Card', value: '1. Adaptive Card' },
{ type: ActionTypes.ImBack, title: '2. Animation Card', value: '2. Animation Card' },
{ type: ActionTypes.ImBack, title: '3. Audio Card', value: '3. Audio Card' },
{ type: ActionTypes.ImBack, title: '4. Hero Card', value: '4. Hero Card' },
{ type: ActionTypes.ImBack, title: '5. Receipt Card', value: '5. Receipt Card' },
{ type: ActionTypes.ImBack, title: '6. Thumbnail Card', value: '6. Thumbnail Card' },
{ type: ActionTypes.ImBack, title: '7. Video Card', value: '7. Video Card' },
]

const card = CardFactory.heroCard('', undefined,
buttons, { text: 'Select one of the following choices' })

await CardMessages.sendActivity(context, card)
}

static async sendAdaptiveCard(context: TurnContext, adaptiveCard: any): Promise<void> {
const card = CardFactory.adaptiveCard(adaptiveCard)

await CardMessages.sendActivity(context, card)
}

static async sendAnimationCard(context: TurnContext): Promise<void> {
const card = CardFactory.animationCard(
'Microsoft Bot Framework',
[
{ url: 'https://i.giphy.com/Ki55RUbOV5njy.gif' }
],
[],
{
subtitle: 'Animation Card'
}
)

await CardMessages.sendActivity(context, card)
}

static async sendAudioCard(context: TurnContext): Promise<void> {
const card = CardFactory.audioCard(
'I am your father',
['https://www.mediacollege.com/downloads/sound-effects/star-wars/darthvader/darthvader_yourfather.wav'],
CardFactory.actions([
{
type: ActionTypes.OpenUrl,
title: 'Read more',
value: 'https://en.wikipedia.org/wiki/The_Empire_Strikes_Back'
}
]),
{
subtitle: 'Star Wars: Episode V - The Empire Strikes Back',
text: 'The Empire Strikes Back (also known as Star Wars: Episode V – The Empire Strikes Back) is a 1980 American epic space opera film directed by Irvin Kershner. Leigh Brackett and Lawrence Kasdan wrote the screenplay, with George Lucas writing the film\'s story and serving as executive producer. The second installment in the original Star Wars trilogy, it was produced by Gary Kurtz for Lucasfilm Ltd. and stars Mark Hamill, Harrison Ford, Carrie Fisher, Billy Dee Williams, Anthony Daniels, David Prowse, Kenny Baker, Peter Mayhew and Frank Oz.',
image: { url: 'https://upload.wikimedia.org/wikipedia/en/3/3c/SW_-_Empire_Strikes_Back.jpg' }
}
)

await CardMessages.sendActivity(context, card)
}

static async sendHeroCard(context: TurnContext): Promise<void> {
const card = CardFactory.heroCard(
'Copilot Hero Card',
CardFactory.images(['https://blogs.microsoft.com/wp-content/uploads/prod/2023/09/Press-Image_FINAL_16x9-4.jpg']),
CardFactory.actions([
{
type: ActionTypes.OpenUrl,
title: 'Get started',
value: 'https://docs.microsoft.com/en-us/azure/bot-service/'
}
])
)

await CardMessages.sendActivity(context, card)
}

static async sendReceiptCard(context: TurnContext): Promise<void> {
const card = CardFactory.receiptCard({
title: 'John Doe',
facts: [
{
key: 'Order Number',
value: '1234'
},
{
key: 'Payment Method',
value: 'VISA 5555-****'
}
],
items: [
{
title: 'Data Transfer',
price: '$38.45',
quantity: 368,
image: { url: 'https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png' }
},
{
title: 'App Service',
price: '$45.00',
quantity: 720,
image: { url: 'https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png' }
}
],
tax: '$7.50',
total: '$90.95',
buttons: CardFactory.actions([
{
type: ActionTypes.OpenUrl,
title: 'More information',
value: 'https://azure.microsoft.com/en-us/pricing/details/bot-service/'
}
])
})

await CardMessages.sendActivity(context, card)
}

static async sendThumbnailCard(context: TurnContext) {
const card = CardFactory.thumbnailCard(
'Copilot Thumbnail Card',
[{ url: 'https://blogs.microsoft.com/wp-content/uploads/prod/2023/09/Press-Image_FINAL_16x9-4.jpg' }],
[{
type: ActionTypes.OpenUrl,
title: 'Get started',
value: 'https://docs.microsoft.com/en-us/azure/bot-service/'
}],
{
subtitle: 'Your bots — wherever your users are talking.',
text: 'Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.'
}
)

await CardMessages.sendActivity(context, card)
}

static async sendVideoCard(context: TurnContext) {
const card = CardFactory.videoCard(
'2018 Imagine Cup World Championship Intro',
[{ url: 'https://sec.ch9.ms/ch9/783d/d57287a5-185f-4df9-aa08-fcab699a783d/IC18WorldChampionshipIntro2.mp4' }],
[{
type: ActionTypes.OpenUrl,
title: 'Lean More',
value: 'https://channel9.msdn.com/Events/Imagine-Cup/World-Finals-2018/2018-Imagine-Cup-World-Championship-Intro'
}],
{
subtitle: 'by Microsoft',
text: 'Microsoft\'s Imagine Cup has empowered student developers around the world to create and innovate on the world stage for the past 16 years. These innovations will shape how we live, work and play.'
}
)

await CardMessages.sendActivity(context, card)
}

private static async sendActivity(context: TurnContext, card: Attachment): Promise<void> {
await context.sendActivity(Activity.fromObject(
{
type: ActivityTypes.Message,
attachments: [card]
}
)
)
}
}
Loading

0 comments on commit e44b3ba

Please sign in to comment.