├── bot ├── run.cjs ├── images │ ├── chatgpt-chat.png │ └── chatgpt-chat-with-context.png ├── .gitignore ├── config.ts ├── .webappignore ├── tsconfig.json ├── adaptiveCards │ └── welcome.json ├── package.json ├── teamsBot.ts ├── index.ts ├── web.config └── README.md ├── .gitattributes ├── templates ├── appPackage │ ├── resources │ │ ├── color.png │ │ └── outline.png │ └── manifest.template.json └── azure │ ├── main.bicep │ ├── config.bicep │ ├── provision │ ├── identity.bicep │ ├── botService.bicep │ └── azureWebAppBot.bicep │ ├── teamsFx │ └── azureWebAppBotConfig.bicep │ └── provision.bicep ├── .gitignore ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── package.json ├── README.md └── .fx └── configs ├── azure.parameters.dev.json ├── config.dev.json └── projectSettings.json /bot/run.cjs: -------------------------------------------------------------------------------- 1 | import("./index.js"); -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.bicep linguist-vendored -------------------------------------------------------------------------------- /bot/images/chatgpt-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formulahendry/chatgpt-teams-bot/HEAD/bot/images/chatgpt-chat.png -------------------------------------------------------------------------------- /bot/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # misc 5 | .env 6 | .deployment 7 | .DS_Store 8 | 9 | # build 10 | lib/ 11 | -------------------------------------------------------------------------------- /bot/images/chatgpt-chat-with-context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formulahendry/chatgpt-teams-bot/HEAD/bot/images/chatgpt-chat-with-context.png -------------------------------------------------------------------------------- /templates/appPackage/resources/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formulahendry/chatgpt-teams-bot/HEAD/templates/appPackage/resources/color.png -------------------------------------------------------------------------------- /templates/appPackage/resources/outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formulahendry/chatgpt-teams-bot/HEAD/templates/appPackage/resources/outline.png -------------------------------------------------------------------------------- /bot/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | botId: process.env.BOT_ID, 3 | botPassword: process.env.BOT_PASSWORD, 4 | openaiApiKey: process.env.OPENAI_API_KEY, 5 | }; 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # TeamsFx files 2 | node_modules 3 | .fx/configs/localSettings.json 4 | .fx/states/*.userdata 5 | .DS_Store 6 | .env.teamsfx.local 7 | subscriptionInfo.json 8 | build 9 | .fx/configs/config.local.json 10 | .fx/states/state.local.json -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug.onTaskErrors": "abort", 3 | "json.schemas": [ 4 | { 5 | "fileMatch": [ 6 | "/aad.*.json" 7 | ], 8 | "schema": {} 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /bot/.webappignore: -------------------------------------------------------------------------------- 1 | .fx 2 | .deployment 3 | .vscode 4 | *.js.map 5 | *.ts.map 6 | *.ts 7 | .git* 8 | .tsbuildinfo 9 | CHANGELOG.md 10 | readme.md 11 | local.settings.json 12 | test 13 | tsconfig.json 14 | .DS_Store 15 | node_modules/.bin 16 | node_modules/ts-node 17 | node_modules/typescript -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-teams-bot", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "devDependencies": { 10 | "@microsoft/teamsfx-cli": "1.*" 11 | }, 12 | "license": "MIT" 13 | } -------------------------------------------------------------------------------- /bot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "target": "ES2017", 5 | "module": "NodeNext", 6 | "outDir": "./lib", 7 | "rootDir": "./", 8 | "sourceMap": true, 9 | "incremental": true, 10 | "tsBuildInfoFile": "./lib/.tsbuildinfo", 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPT Teams Bot app 2 | 3 | This is a ChatGPT Teams Bot app which uses latest `gpt-3.5-turbo` model optimized for chat. `Turbo` is the same model family that powers ChatGPT. 4 | 5 | Follow [this guide](./bot/README.md) to get started. 6 | 7 | You could also try the [ChatGPT WeChat Bot](https://github.com/formulahendry/chatgpt-wechat-bot). 8 | 9 | ![ChatGPT](./bot/images/chatgpt-chat-with-context.png) -------------------------------------------------------------------------------- /.fx/configs/azure.parameters.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "provisionParameters": { 6 | "value": { 7 | "botAadAppClientId": "{{state.teams-bot.botId}}", 8 | "botAadAppClientSecret": "{{state.teams-bot.botPassword}}", 9 | "openaiApiKey": "{{$env.OPENAI_API_KEY}}", 10 | "resourceBaseName": "chatgptbot{{state.solution.resourceNameSuffix}}", 11 | "botDisplayName": "chatgpt-bot" 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /templates/azure/main.bicep: -------------------------------------------------------------------------------- 1 | @secure() 2 | param provisionParameters object 3 | 4 | module provision './provision.bicep' = { 5 | name: 'provisionResources' 6 | params: { 7 | provisionParameters: provisionParameters 8 | } 9 | } 10 | output provisionOutput object = provision 11 | module config './config.bicep' = { 12 | name: 'configureResources' 13 | params: { 14 | provisionParameters: provisionParameters 15 | provisionOutputs: provision 16 | } 17 | } 18 | output configOutput object = contains(reference(resourceId('Microsoft.Resources/deployments', config.name), '2020-06-01'), 'outputs') ? config : {} 19 | -------------------------------------------------------------------------------- /templates/azure/config.bicep: -------------------------------------------------------------------------------- 1 | @secure() 2 | param provisionParameters object 3 | param provisionOutputs object 4 | 5 | // Get existing app settings for merge 6 | var currentAppSettings = list('${ provisionOutputs.azureWebAppBotOutput.value.resourceId }/config/appsettings', '2021-02-01').properties 7 | 8 | // Merge TeamsFx configurations to Bot resources 9 | module teamsFxAzureWebAppBotConfig './teamsFx/azureWebAppBotConfig.bicep' = { 10 | name: 'teamsFxAzureWebAppBotConfig' 11 | params: { 12 | provisionParameters: provisionParameters 13 | provisionOutputs: provisionOutputs 14 | currentAppSettings: currentAppSettings 15 | } 16 | } -------------------------------------------------------------------------------- /.fx/configs/config.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://aka.ms/teamsfx-env-config-schema", 3 | "description": "You can customize the TeamsFx config for different environments. Visit https://aka.ms/teamsfx-env-config to learn more about this.", 4 | "manifest": { 5 | "appName": { 6 | "short": "chatgpt-teams-bot", 7 | "full": "Full name for chatgpt-teams-bot" 8 | }, 9 | "description": { 10 | "short": "Short description of chatgpt-teams-bot", 11 | "full": "Full description of chatgpt-teams-bot" 12 | }, 13 | "icons": { 14 | "color": "resources/color.png", 15 | "outline": "resources/outline.png" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /templates/azure/provision/identity.bicep: -------------------------------------------------------------------------------- 1 | @secure() 2 | param provisionParameters object 3 | var resourceBaseName = provisionParameters.resourceBaseName 4 | var identityName = contains(provisionParameters, 'userAssignedIdentityName') ? provisionParameters['userAssignedIdentityName'] : '${resourceBaseName}' // Try to read name for user assigned identity from parameters 5 | 6 | // user assigned identity will be used to access other Azure resources 7 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { 8 | name: identityName 9 | location: resourceGroup().location 10 | } 11 | 12 | output identityName string = identityName 13 | output identityClientId string = managedIdentity.properties.clientId 14 | output identityResourceId string = managedIdentity.id 15 | output identityPrincipalId string = managedIdentity.properties.principalId -------------------------------------------------------------------------------- /bot/adaptiveCards/welcome.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "AdaptiveCard", 3 | "body": [ 4 | { 5 | "type": "TextBlock", 6 | "size": "Medium", 7 | "weight": "Bolder", 8 | "text": "Your Hello World Bot is Running" 9 | }, 10 | { 11 | "type": "TextBlock", 12 | "text": "Congratulations! Your hello world bot is running. Click the documentation below to learn more about Bots and the Teams Toolkit.", 13 | "wrap": true 14 | } 15 | ], 16 | "actions": [ 17 | { 18 | "type": "Action.OpenUrl", 19 | "title": "Bot Framework Docs", 20 | "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" 21 | }, 22 | { 23 | "type": "Action.OpenUrl", 24 | "title": "Teams Toolkit Docs", 25 | "url": "https://aka.ms/teamsfx-docs" 26 | } 27 | ], 28 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 29 | "version": "1.4" 30 | } 31 | -------------------------------------------------------------------------------- /templates/azure/teamsFx/azureWebAppBotConfig.bicep: -------------------------------------------------------------------------------- 1 | // Auto generated content, please customize files under provision folder 2 | 3 | @secure() 4 | param provisionParameters object 5 | param provisionOutputs object 6 | @secure() 7 | param currentAppSettings object 8 | 9 | var webAppName = split(provisionOutputs.azureWebAppBotOutput.value.resourceId, '/')[8] 10 | var botAadAppClientId = provisionParameters['botAadAppClientId'] 11 | var botAadAppClientSecret = provisionParameters['botAadAppClientSecret'] 12 | 13 | resource webAppSettings 'Microsoft.Web/sites/config@2021-02-01' = { 14 | name: '${webAppName}/appsettings' 15 | properties: union({ 16 | BOT_ID: botAadAppClientId // ID of your bot 17 | BOT_PASSWORD: botAadAppClientSecret // Secret of your bot 18 | IDENTITY_ID: provisionOutputs.identityOutput.value.identityClientId // User assigned identity id, the identity is used to access other Azure resources 19 | }, currentAppSettings) 20 | } -------------------------------------------------------------------------------- /bot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "echobot", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "Microsoft Teams Toolkit hello world Bot sample", 6 | "author": "Microsoft", 7 | "license": "MIT", 8 | "main": "./lib/index.js", 9 | "scripts": { 10 | "dev:teamsfx": "env-cmd --silent -f .env.teamsfx.local npm run dev", 11 | "dev": "nodemon --exec node --inspect=9239 --signal SIGINT -r ts-node/register --loader ts-node/esm ./index.ts", 12 | "build": "tsc --build && shx cp -r ./adaptiveCards ./lib/ && shx cp run.cjs ./lib/", 13 | "start": "node ./lib/index.js", 14 | "watch": "nodemon --exec \"npm run start\"", 15 | "test": "echo \"Error: no test specified\" && exit 1" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com" 20 | }, 21 | "dependencies": { 22 | "@microsoft/adaptivecards-tools": "^1.0.0", 23 | "botbuilder": "^4.18.0", 24 | "chatgpt": "^5.0.4", 25 | "express": "^4.18.2", 26 | "restify": "^8.5.1" 27 | }, 28 | "devDependencies": { 29 | "@types/express": "^4.17.14", 30 | "@types/restify": "8.4.2", 31 | "env-cmd": "^10.1.0", 32 | "nodemon": "^2.0.7", 33 | "shx": "^0.3.3", 34 | "ts-node": "^10.4.0", 35 | "typescript": "^4.4.4" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /templates/azure/provision/botService.bicep: -------------------------------------------------------------------------------- 1 | @secure() 2 | param provisionParameters object 3 | param botEndpoint string 4 | var resourceBaseName = provisionParameters.resourceBaseName 5 | var botAadAppClientId = provisionParameters['botAadAppClientId'] // Read AAD app client id for Azure Bot Service from parameters 6 | var botServiceName = contains(provisionParameters, 'botServiceName') ? provisionParameters['botServiceName'] : '${resourceBaseName}' // Try to read name for Azure Bot Service from parameters 7 | var botServiceSku = contains(provisionParameters, 'botServiceSku') ? provisionParameters['botServiceSku'] : 'F0' // Try to read SKU for Azure Bot Service from parameters 8 | var botDisplayName = contains(provisionParameters, 'botDisplayName') ? provisionParameters['botDisplayName'] : '${resourceBaseName}' // Try to read display name for Azure Bot Service from parameters 9 | 10 | // Register your web service as a bot with the Bot Framework 11 | resource azureBot 'Microsoft.BotService/botServices@2021-03-01' = { 12 | kind: 'azurebot' 13 | location: 'global' 14 | name: botServiceName 15 | properties: { 16 | displayName: botDisplayName 17 | endpoint: uri(botEndpoint, '/api/messages') 18 | msaAppId: botAadAppClientId 19 | } 20 | sku: { 21 | name: botServiceSku // You can follow https://aka.ms/teamsfx-bicep-add-param-tutorial to add botServiceSku property to provisionParameters to override the default value "F0". 22 | } 23 | } 24 | 25 | // Connect the bot service to Microsoft Teams 26 | resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { 27 | parent: azureBot 28 | location: 'global' 29 | name: 'MsTeamsChannel' 30 | properties: { 31 | channelName: 'MsTeamsChannel' 32 | } 33 | } -------------------------------------------------------------------------------- /.fx/configs/projectSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "chatgpt-teams-bot", 3 | "projectId": "5758b4c5-1d35-482a-af71-5a4546396689", 4 | "version": "2.1.0", 5 | "components": [ 6 | { 7 | "name": "teams-bot", 8 | "hosting": "azure-web-app", 9 | "provision": false, 10 | "deploy": true, 11 | "capabilities": [ 12 | "bot" 13 | ], 14 | "build": true, 15 | "folder": "bot", 16 | "artifactFolder": "bot" 17 | }, 18 | { 19 | "name": "bot-service", 20 | "provision": true 21 | }, 22 | { 23 | "name": "azure-web-app", 24 | "scenario": "Bot", 25 | "connections": [ 26 | "identity", 27 | "teams-bot" 28 | ] 29 | }, 30 | { 31 | "name": "identity", 32 | "provision": true 33 | } 34 | ], 35 | "programmingLanguage": "typescript", 36 | "solutionSettings": { 37 | "name": "fx-solution-azure", 38 | "version": "1.0.0", 39 | "hostType": "Azure", 40 | "azureResources": [], 41 | "capabilities": [ 42 | "Bot" 43 | ], 44 | "activeResourcePlugins": [ 45 | "fx-resource-local-debug", 46 | "fx-resource-appstudio", 47 | "fx-resource-cicd", 48 | "fx-resource-api-connector", 49 | "fx-resource-bot", 50 | "fx-resource-identity" 51 | ] 52 | }, 53 | "pluginSettings": { 54 | "fx-resource-bot": { 55 | "host-type": "app-service", 56 | "capabilities": [ 57 | "bot" 58 | ] 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /bot/teamsBot.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TeamsActivityHandler, 3 | CardFactory, 4 | TurnContext, 5 | } from "botbuilder"; 6 | import rawWelcomeCard from "./adaptiveCards/welcome.json" assert { type: "json" }; 7 | import { AdaptiveCards } from "@microsoft/adaptivecards-tools"; 8 | import { ChatGPTAPI } from 'chatgpt'; 9 | import config from "./config.js"; 10 | 11 | export class TeamsBot extends TeamsActivityHandler { 12 | 13 | constructor() { 14 | super(); 15 | 16 | const api = new ChatGPTAPI({ 17 | apiKey: config.openaiApiKey, 18 | }); 19 | 20 | let parentMessageId; 21 | 22 | this.onMessage(async (context, next) => { 23 | console.log("Running with Message Activity."); 24 | 25 | let txt = context.activity.text; 26 | const removedMentionText = TurnContext.removeRecipientMention(context.activity); 27 | if (removedMentionText) { 28 | // Remove the line break 29 | txt = removedMentionText.toLowerCase().replace(/\n|\r/g, "").trim(); 30 | } 31 | 32 | const response = await api.sendMessage(txt, { 33 | parentMessageId 34 | }); 35 | 36 | parentMessageId = response.id; 37 | 38 | await context.sendActivity(response.text); 39 | 40 | // By calling next() you ensure that the next BotHandler is run. 41 | await next(); 42 | }); 43 | 44 | this.onMembersAdded(async (context, next) => { 45 | const membersAdded = context.activity.membersAdded; 46 | for (let cnt = 0; cnt < membersAdded.length; cnt++) { 47 | if (membersAdded[cnt].id) { 48 | const card = AdaptiveCards.declareWithoutData(rawWelcomeCard).render(); 49 | await context.sendActivity({ attachments: [CardFactory.adaptiveCard(card)] }); 50 | break; 51 | } 52 | } 53 | await next(); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /templates/azure/provision.bicep: -------------------------------------------------------------------------------- 1 | @secure() 2 | param provisionParameters object 3 | 4 | // Merge TeamsFx configurations to Bot service 5 | module botProvision './provision/botService.bicep' = { 6 | name: 'botProvision' 7 | params: { 8 | provisionParameters: provisionParameters 9 | botEndpoint: azureWebAppBotProvision.outputs.siteEndpoint 10 | } 11 | } 12 | 13 | // Resources web app 14 | module azureWebAppBotProvision './provision/azureWebAppBot.bicep' = { 15 | name: 'azureWebAppBotProvision' 16 | params: { 17 | provisionParameters: provisionParameters 18 | userAssignedIdentityId: userAssignedIdentityProvision.outputs.identityResourceId 19 | } 20 | } 21 | 22 | 23 | output azureWebAppBotOutput object = { 24 | teamsFxPluginId: 'teams-bot' 25 | skuName: azureWebAppBotProvision.outputs.skuName 26 | siteName: azureWebAppBotProvision.outputs.siteName 27 | domain: azureWebAppBotProvision.outputs.domain 28 | appServicePlanName: azureWebAppBotProvision.outputs.appServicePlanName 29 | resourceId: azureWebAppBotProvision.outputs.resourceId 30 | siteEndpoint: azureWebAppBotProvision.outputs.siteEndpoint 31 | } 32 | 33 | output BotOutput object = { 34 | domain: azureWebAppBotProvision.outputs.domain 35 | endpoint: azureWebAppBotProvision.outputs.siteEndpoint 36 | } 37 | 38 | // Resources for identity 39 | module userAssignedIdentityProvision './provision/identity.bicep' = { 40 | name: 'userAssignedIdentityProvision' 41 | params: { 42 | provisionParameters: provisionParameters 43 | } 44 | } 45 | 46 | output identityOutput object = { 47 | teamsFxPluginId: 'identity' 48 | identityName: userAssignedIdentityProvision.outputs.identityName 49 | identityResourceId: userAssignedIdentityProvision.outputs.identityResourceId 50 | identityClientId: userAssignedIdentityProvision.outputs.identityClientId 51 | } -------------------------------------------------------------------------------- /templates/appPackage/manifest.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.14/MicrosoftTeams.schema.json", 3 | "manifestVersion": "1.14", 4 | "version": "1.0.0", 5 | "id": "{{state.fx-resource-appstudio.teamsAppId}}", 6 | "packageName": "com.microsoft.teams.extension", 7 | "developer": { 8 | "name": "Teams App, Inc.", 9 | "websiteUrl": "https://www.example.com", 10 | "privacyUrl": "https://www.example.com/termofuse", 11 | "termsOfUseUrl": "https://www.example.com/privacy" 12 | }, 13 | "icons": { 14 | "color": "{{config.manifest.icons.color}}", 15 | "outline": "{{config.manifest.icons.outline}}" 16 | }, 17 | "name": { 18 | "short": "{{config.manifest.appName.short}}", 19 | "full": "{{config.manifest.appName.full}}" 20 | }, 21 | "description": { 22 | "short": "{{config.manifest.description.short}}", 23 | "full": "{{config.manifest.description.full}}" 24 | }, 25 | "accentColor": "#FFFFFF", 26 | "bots": [ 27 | { 28 | "botId": "{{state.fx-resource-bot.botId}}", 29 | "scopes": [ 30 | "personal", 31 | "team", 32 | "groupchat" 33 | ], 34 | "supportsFiles": false, 35 | "isNotificationOnly": false, 36 | "commandLists": [ 37 | { 38 | "scopes": [ 39 | "personal", 40 | "team", 41 | "groupchat" 42 | ], 43 | "commands": [] 44 | } 45 | ] 46 | } 47 | ], 48 | "composeExtensions": [], 49 | "configurableTabs": [], 50 | "staticTabs": [], 51 | "permissions": [ 52 | "identity", 53 | "messageTeamMembers" 54 | ], 55 | "validDomains": [ 56 | "{{state.fx-resource-bot.domain}}" 57 | ] 58 | } -------------------------------------------------------------------------------- /bot/index.ts: -------------------------------------------------------------------------------- 1 | // Import required packages 2 | import express from "express"; 3 | 4 | // Import required bot services. 5 | // See https://aka.ms/bot-services to learn more about the different parts of a bot. 6 | import { BotFrameworkAdapter, TurnContext } from "botbuilder"; 7 | 8 | // This bot's main dialog. 9 | import { TeamsBot } from "./teamsBot.js"; 10 | import config from "./config.js"; 11 | 12 | // Create adapter. 13 | // See https://aka.ms/about-bot-adapter to learn more about adapters. 14 | const adapter = new BotFrameworkAdapter({ 15 | appId: config.botId, 16 | appPassword: config.botPassword, 17 | }); 18 | 19 | // Catch-all for errors. 20 | const onTurnErrorHandler = async (context: TurnContext, error: Error) => { 21 | // This check writes out errors to console log .vs. app insights. 22 | // NOTE: In production environment, you should consider logging this to Azure 23 | // application insights. 24 | console.error(`\n [onTurnError] unhandled error: ${error}`); 25 | 26 | // Send a trace activity, which will be displayed in Bot Framework Emulator 27 | await context.sendTraceActivity( 28 | "OnTurnError Trace", 29 | `${error}`, 30 | "https://www.botframework.com/schemas/error", 31 | "TurnError" 32 | ); 33 | 34 | // Send a message to the user 35 | await context.sendActivity(`The bot encountered unhandled error:\n ${error.message}`); 36 | await context.sendActivity("To continue to run this bot, please fix the bot source code."); 37 | }; 38 | 39 | // Set the onTurnError for the singleton BotFrameworkAdapter. 40 | adapter.onTurnError = onTurnErrorHandler; 41 | 42 | // Create the bot that will handle incoming messages. 43 | const bot = new TeamsBot(); 44 | 45 | // Create HTTP server. 46 | const server = express(); 47 | 48 | server.listen(process.env.port || process.env.PORT || 3978, () => { 49 | console.log(`\nBot Started, express server is runnning.`); 50 | }); 51 | 52 | // Listen for incoming requests. 53 | server.post("/api/messages", async (req, res) => { 54 | await adapter.processActivity(req, res, async (context) => { 55 | await bot.run(context); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /templates/azure/provision/azureWebAppBot.bicep: -------------------------------------------------------------------------------- 1 | @secure() 2 | param provisionParameters object 3 | param userAssignedIdentityId string 4 | 5 | var resourceBaseName = provisionParameters.resourceBaseName 6 | var openaiApiKey = provisionParameters.openaiApiKey 7 | var serverfarmsName = contains(provisionParameters, 'webAppServerfarmsName') ? provisionParameters['webAppServerfarmsName'] : '${resourceBaseName}bot' // Try to read name for App Service Plan from parameters 8 | var webAppSKU = contains(provisionParameters, 'webAppSKU') ? provisionParameters['webAppSKU'] : 'B1' // Try to read SKU for Azure Web App from parameters 9 | var webAppName = contains(provisionParameters, 'webAppSitesName') ? provisionParameters['webAppSitesName'] : '${resourceBaseName}bot' // Try to read name for Azure Web App from parameters 10 | 11 | // Compute resources for your Web App 12 | resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { 13 | kind: 'app' 14 | location: resourceGroup().location 15 | name: serverfarmsName 16 | sku: { 17 | name: webAppSKU 18 | } 19 | properties: {} 20 | } 21 | 22 | // Web App that hosts your app 23 | resource webApp 'Microsoft.Web/sites@2021-02-01' = { 24 | kind: 'app' 25 | location: resourceGroup().location 26 | name: webAppName 27 | properties: { 28 | serverFarmId: serverfarm.id 29 | keyVaultReferenceIdentity: userAssignedIdentityId // Use given user assigned identity to access Key Vault 30 | httpsOnly: true 31 | siteConfig: { 32 | alwaysOn: true 33 | appSettings: [ 34 | { 35 | name: 'WEBSITE_NODE_DEFAULT_VERSION' 36 | value: '~18' // Set NodeJS version to 18.x for your site 37 | } 38 | { 39 | name: 'SCM_SCRIPT_GENERATOR_ARGS' 40 | value: '--node' // Register as node server 41 | } 42 | { 43 | name: 'RUNNING_ON_AZURE' 44 | value: '1' 45 | } 46 | { 47 | name: 'OPENAI_API_KEY' 48 | value: openaiApiKey 49 | } 50 | ] 51 | } 52 | } 53 | identity: { 54 | type: 'UserAssigned' 55 | userAssignedIdentities: { 56 | '${userAssignedIdentityId}': {} // The identity is used to access other Azure resources 57 | } 58 | } 59 | } 60 | 61 | output skuName string = webAppSKU 62 | output siteName string = webAppName 63 | output domain string = webApp.properties.defaultHostName 64 | output appServicePlanName string = serverfarmsName 65 | output resourceId string = webApp.id 66 | output siteEndpoint string = 'https://${webApp.properties.defaultHostName}' 67 | -------------------------------------------------------------------------------- /bot/web.config: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Remote (Edge)", 6 | "type": "pwa-msedge", 7 | "request": "launch", 8 | "url": "https://teams.microsoft.com/l/app/${teamsAppId}?installAppPackage=true&webjoin=true&${account-hint}", 9 | "presentation": { 10 | "group": "remote", 11 | "order": 1 12 | }, 13 | "internalConsoleOptions": "neverOpen" 14 | }, 15 | { 16 | "name": "Launch Remote (Chrome)", 17 | "type": "pwa-chrome", 18 | "request": "launch", 19 | "url": "https://teams.microsoft.com/l/app/${teamsAppId}?installAppPackage=true&webjoin=true&${account-hint}", 20 | "presentation": { 21 | "group": "remote", 22 | "order": 2 23 | }, 24 | "internalConsoleOptions": "neverOpen" 25 | }, 26 | { 27 | "name": "Launch Bot (Edge)", 28 | "type": "pwa-msedge", 29 | "request": "launch", 30 | "url": "https://teams.microsoft.com/l/app/${localTeamsAppId}?installAppPackage=true&webjoin=true&${account-hint}", 31 | "cascadeTerminateToConfigurations": [ 32 | "Attach to Bot" 33 | ], 34 | "presentation": { 35 | "group": "all", 36 | "hidden": true 37 | }, 38 | "internalConsoleOptions": "neverOpen" 39 | }, 40 | { 41 | "name": "Launch Bot (Chrome)", 42 | "type": "pwa-chrome", 43 | "request": "launch", 44 | "url": "https://teams.microsoft.com/l/app/${localTeamsAppId}?installAppPackage=true&webjoin=true&${account-hint}", 45 | "cascadeTerminateToConfigurations": [ 46 | "Attach to Bot" 47 | ], 48 | "presentation": { 49 | "group": "all", 50 | "hidden": true 51 | }, 52 | "internalConsoleOptions": "neverOpen" 53 | }, 54 | { 55 | "name": "Attach to Bot", 56 | "type": "pwa-node", 57 | "request": "attach", 58 | "port": 9239, 59 | "restart": true, 60 | "presentation": { 61 | "group": "all", 62 | "hidden": true 63 | }, 64 | "internalConsoleOptions": "neverOpen" 65 | } 66 | ], 67 | "compounds": [ 68 | { 69 | "name": "Debug (Edge)", 70 | "configurations": [ 71 | "Launch Bot (Edge)", 72 | "Attach to Bot" 73 | ], 74 | "preLaunchTask": "Start Teams App Locally", 75 | "presentation": { 76 | "group": "all", 77 | "order": 1 78 | }, 79 | "stopAll": true 80 | }, 81 | { 82 | "name": "Debug (Chrome)", 83 | "configurations": [ 84 | "Launch Bot (Chrome)", 85 | "Attach to Bot" 86 | ], 87 | "preLaunchTask": "Start Teams App Locally", 88 | "presentation": { 89 | "group": "all", 90 | "order": 2 91 | }, 92 | "stopAll": true 93 | } 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /bot/README.md: -------------------------------------------------------------------------------- 1 | # How to use this ChatGPT Teams Bot app 2 | 3 | This is a ChatGPT Teams Bot app to let you chat with ChatGPT in Microsoft Teams. 4 | 5 | You could also try the [ChatGPT WeChat Bot](https://github.com/formulahendry/chatgpt-wechat-bot). 6 | 7 | ![ChatGPT](./images/chatgpt-chat.png) 8 | 9 | ## Prerequisites 10 | 11 | - An [OpenAI](https://openai.com/api/) account 12 | - [NodeJS](https://nodejs.org/en/) (Tested on Node.js 18.12.1) 13 | - An M365 account. If you do not have M365 account, apply one from [M365 developer program](https://developer.microsoft.com/en-us/microsoft-365/dev-program) 14 | - Latest stable version of [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) (Tested on version 4.1.3) 15 | 16 | ## Get API key 17 | 18 | Get an OpenAI API key from https://beta.openai.com/account/api-keys. 19 | 20 | ## Debug 21 | 22 | - Create a `.env.teamsfx.local` file under `bot` folder, and set the OpenAI API key in `.env.teamsfx.local` file: 23 | ``` 24 | OPENAI_API_KEY=xxxxxxxxxx 25 | ``` 26 | - From Visual Studio Code: Start debugging the project by hitting the `F5` key in Visual Studio Code. 27 | - Alternatively use the `Run and Debug Activity Panel` in Visual Studio Code and click the `Run and Debug` green arrow button. 28 | - From TeamsFx CLI: Start debugging the project by executing the command `teamsfx preview --local` in your project directory. 29 | 30 | ## Deploy to Azure 31 | 32 | First, set `OPENAI_API_KEY` in envionment variables of your OS. 33 | 34 | Then, deploy your project to Azure by following these steps: 35 | 36 | | From Visual Studio Code | From TeamsFx CLI | 37 | | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 38 | | | | 39 | 40 | > Note: Provisioning and deployment may incur charges to your Azure Subscription. 41 | 42 | ## Preview 43 | 44 | Once the provisioning and deployment steps are finished, you can preview your app: 45 | 46 | - From Visual Studio Code 47 | 48 | 1. Open the `Run and Debug Activity Panel`. 49 | 1. Select `Launch Remote (Edge)` or `Launch Remote (Chrome)` from the launch configuration drop-down. 50 | 1. Press the Play (green arrow) button to launch your app - now running remotely from Azure. 51 | 52 | - From TeamsFx CLI: execute `teamsfx preview --remote` in your project directory to launch your application. 53 | 54 | ## Further reading 55 | 56 | - [chatgpt-api Node SDK](https://github.com/transitive-bullshit/chatgpt-api): it is a 3rd-party ChatGPT npm used in this Teams bot. 57 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // This file is automatically generated by Teams Toolkit. 2 | // The teamsfx tasks defined in this file require Teams Toolkit version >= 4.1.0. 3 | // See https://aka.ms/teamsfx-debug-tasks for details on how to customize each task and how to integrate with existing Teams Toolkit projects. 4 | { 5 | "version": "2.0.0", 6 | "tasks": [ 7 | { 8 | "label": "Start Teams App Locally", 9 | "dependsOn": [ 10 | "Validate & install prerequisites", 11 | "Install npm packages", 12 | "Start local tunnel", 13 | "Set up bot", 14 | "Build & upload Teams manifest", 15 | "Start services" 16 | ], 17 | "dependsOrder": "sequence" 18 | }, 19 | { 20 | // Check if all required prerequisites are installed and will install them if not. 21 | // See https://aka.ms/teamsfx-check-prerequisites-task to know the details and how to customize the args. 22 | "label": "Validate & install prerequisites", 23 | "type": "teamsfx", 24 | "command": "debug-check-prerequisites", 25 | "args": { 26 | "prerequisites": [ 27 | "nodejs", // Validate if Node.js is installed. 28 | "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. 29 | "ngrok", // Install Ngrok. Bot project requires a public message endpoint, and ngrok can help create public tunnel for your local service. 30 | "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. 31 | ], 32 | "portOccupancy": [ 33 | 3978, // bot service port 34 | 9239 // bot inspector port for Node.js debugger 35 | ] 36 | } 37 | }, 38 | { 39 | // Check if all the npm packages are installed and will install them if not. 40 | // See https://aka.ms/teamsfx-npm-package-task to know the details and how to customize the args. 41 | "label": "Install npm packages", 42 | "type": "teamsfx", 43 | "command": "debug-npm-install", 44 | "args": { 45 | "projects": [ 46 | { 47 | "cwd": "${workspaceFolder}/bot", 48 | "npmInstallArgs": [ 49 | "--no-audit" 50 | ] 51 | } 52 | ] 53 | } 54 | }, 55 | { 56 | // Start the local tunnel service to forward public ngrok URL to local port and inspect traffic. 57 | // See https://aka.ms/teamsfx-local-tunnel-task to know the details and how to customize the args. 58 | "label": "Start local tunnel", 59 | "type": "teamsfx", 60 | "command": "debug-start-local-tunnel", 61 | "args": { 62 | "ngrokArgs": "http 3978 --log=stdout --log-format=logfmt" 63 | }, 64 | "isBackground": true, 65 | "problemMatcher": "$teamsfx-local-tunnel-watch" 66 | }, 67 | { 68 | // Register resources and prepare local launch information for Bot. 69 | // See https://aka.ms/teamsfx-debug-set-up-bot-task to know the details and how to customize the args. 70 | "label": "Set up bot", 71 | "type": "teamsfx", 72 | "command": "debug-set-up-bot", 73 | "args": { 74 | //// Enter your own bot information if using the existing bot. //// 75 | // "botId": "", 76 | // "botPassword": "", // use plain text or environment variable reference like ${env:BOT_PASSWORD} 77 | "botMessagingEndpoint": "/api/messages" // use your own routing "/any/path", or full URL "https://contoso.com/any/path" 78 | } 79 | }, 80 | { 81 | // Build and upload Teams manifest. 82 | // See https://aka.ms/teamsfx-debug-prepare-manifest-task to know the details and how to customize the args. 83 | "label": "Build & upload Teams manifest", 84 | "type": "teamsfx", 85 | "command": "debug-prepare-manifest", 86 | "args": { 87 | //// Enter your own Teams app package path if using the existing Teams manifest. //// 88 | // "appPackagePath": "" 89 | } 90 | }, 91 | { 92 | "label": "Start services", 93 | "dependsOn": [ 94 | "Start bot" 95 | ] 96 | }, 97 | { 98 | "label": "Start bot", 99 | "type": "shell", 100 | "command": "npm run dev:teamsfx", 101 | "isBackground": true, 102 | "options": { 103 | "cwd": "${workspaceFolder}/bot" 104 | }, 105 | "problemMatcher": { 106 | "pattern": [ 107 | { 108 | "regexp": "^.*$", 109 | "file": 0, 110 | "location": 1, 111 | "message": 2 112 | } 113 | ], 114 | "background": { 115 | "activeOnStart": true, 116 | "beginsPattern": "[nodemon] starting", 117 | "endsPattern": "express server is runnning|Bot/ME service listening at|[nodemon] app crashed" 118 | } 119 | } 120 | } 121 | ] 122 | } --------------------------------------------------------------------------------