diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 73519c049c..e2c85b10d8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -277,6 +277,9 @@ /templates/**/custom-copilot-assistant-assistants-api @kimizhu @swatDong @kuojianlu @XiaofuHuang /templates/**/custom-copilot-assistant-new @kimizhu @swatDong @kuojianlu @XiaofuHuang /templates/**/custom-copilot-basic @kimizhu @swatDong @kuojianlu @XiaofuHuang +/templates/**/custom-copilot-rag-azure-ai-search @kimizhu @swatDong @kuojianlu @XiaofuHuang @xiaolang124 +/templates/**/custom-copilot-rag-customize @kimizhu @swatDong @kuojianlu @XiaofuHuang @xiaolang124 +/templates/**/custom-copilot-rag-microsoft365 @kimizhu @swatDong @kuojianlu @XiaofuHuang @xiaolang124 /templates/**/dashboard-tab @hund030 @eriolchan @huimiu /templates/**/default-bot @JerryYangKai @eriolchan @Siglud @Yukun-dong /templates/**/default-bot-message-extension @yuqizhou77 diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 673380dcb0..d9d9254949 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -54,7 +54,7 @@ jobs: - name: CodeCov report attempt 1 id: codecov1 continue-on-error: true - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true diff --git a/README.md b/README.md index 4ef2bffcec..75f9b33508 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ provided by the bot. You will only need to do this once across all repos using o ## Telemetry -Teams Toolkit collects usage data and sends it to Microsoft to help improve our products and services. Read our [Privacy Statement](https://privacy.microsoft.com/privacystatement) and [Data Collection Notice](https://docs.opensource.microsoft.com/content/releasing/telemetry.html) to learn more. Learn more in our [FAQ](https://code.visualstudio.com/docs/supporting/faq#_how-to-disable-telemetry-reporting). +Teams Toolkit collects usage data and sends it to Microsoft to help improve our products and services. Read our [Privacy Statement](https://go.microsoft.com/fwlink/?LinkId=521839) and [Data Collection Notice](https://docs.opensource.microsoft.com/content/releasing/telemetry.html) to learn more. Learn more in our [FAQ](https://code.visualstudio.com/docs/supporting/faq#_how-to-disable-telemetry-reporting). ## Trademarks diff --git a/packages/api/package.json b/packages/api/package.json index e7ddd85165..419849c2b9 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -45,7 +45,7 @@ "chai-spies": "^1.0.0", "copyfiles": "^2.4.1", "cpy-cli": "^4.0.0", - "eslint": "^7.22.0", + "eslint": "^7.29.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.25.2", "eslint-plugin-no-secrets": "^0.8.9", diff --git a/packages/api/pnpm-lock.yaml b/packages/api/pnpm-lock.yaml index 18672fe288..01334c1dac 100644 --- a/packages/api/pnpm-lock.yaml +++ b/packages/api/pnpm-lock.yaml @@ -60,10 +60,10 @@ devDependencies: version: 9.0.10 '@typescript-eslint/eslint-plugin': specifier: ^4.19.0 - version: 4.19.0(@typescript-eslint/parser@4.19.0)(eslint@7.22.0)(typescript@5.0.4) + version: 4.19.0(@typescript-eslint/parser@4.19.0)(eslint@7.29.0)(typescript@5.0.4) '@typescript-eslint/parser': specifier: ^4.19.0 - version: 4.19.0(eslint@7.22.0)(typescript@5.0.4) + version: 4.19.0(eslint@7.29.0)(typescript@5.0.4) chai-as-promised: specifier: ^7.1.1 version: 7.1.1(chai@4.3.4) @@ -77,20 +77,20 @@ devDependencies: specifier: ^4.0.0 version: 4.0.0 eslint: - specifier: ^7.22.0 - version: 7.22.0 + specifier: ^7.29.0 + version: 7.29.0 eslint-plugin-header: specifier: ^3.1.1 - version: 3.1.1(eslint@7.22.0) + version: 3.1.1(eslint@7.29.0) eslint-plugin-import: specifier: ^2.25.2 - version: 2.25.2(@typescript-eslint/parser@4.19.0)(eslint@7.22.0) + version: 2.25.2(@typescript-eslint/parser@4.19.0)(eslint@7.29.0) eslint-plugin-no-secrets: specifier: ^0.8.9 - version: 0.8.9(eslint@7.22.0) + version: 0.8.9(eslint@7.29.0) eslint-plugin-prettier: specifier: ^4.0.0 - version: 4.0.0(eslint@7.22.0)(prettier@2.4.1) + version: 4.0.0(eslint@7.29.0)(prettier@2.4.1) json-schema-to-typescript: specifier: ^10.1.4 version: 10.1.4 @@ -695,7 +695,7 @@ packages: resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} dev: true - /@typescript-eslint/eslint-plugin@4.19.0(@typescript-eslint/parser@4.19.0)(eslint@7.22.0)(typescript@5.0.4): + /@typescript-eslint/eslint-plugin@4.19.0(@typescript-eslint/parser@4.19.0)(eslint@7.29.0)(typescript@5.0.4): resolution: {integrity: sha512-CRQNQ0mC2Pa7VLwKFbrGVTArfdVDdefS+gTw0oC98vSI98IX5A8EVH4BzJ2FOB0YlCmm8Im36Elad/Jgtvveaw==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -706,11 +706,11 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/experimental-utils': 4.19.0(eslint@7.22.0)(typescript@5.0.4) - '@typescript-eslint/parser': 4.19.0(eslint@7.22.0)(typescript@5.0.4) + '@typescript-eslint/experimental-utils': 4.19.0(eslint@7.29.0)(typescript@5.0.4) + '@typescript-eslint/parser': 4.19.0(eslint@7.29.0)(typescript@5.0.4) '@typescript-eslint/scope-manager': 4.19.0 debug: 4.3.4 - eslint: 7.22.0 + eslint: 7.29.0 functional-red-black-tree: 1.0.1 lodash: 4.17.21 regexpp: 3.2.0 @@ -721,7 +721,7 @@ packages: - supports-color dev: true - /@typescript-eslint/experimental-utils@4.19.0(eslint@7.22.0)(typescript@5.0.4): + /@typescript-eslint/experimental-utils@4.19.0(eslint@7.29.0)(typescript@5.0.4): resolution: {integrity: sha512-9/23F1nnyzbHKuoTqFN1iXwN3bvOm/PRIXSBR3qFAYotK/0LveEOHr5JT1WZSzcD6BESl8kPOG3OoDRKO84bHA==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -731,7 +731,7 @@ packages: '@typescript-eslint/scope-manager': 4.19.0 '@typescript-eslint/types': 4.19.0 '@typescript-eslint/typescript-estree': 4.19.0(typescript@5.0.4) - eslint: 7.22.0 + eslint: 7.29.0 eslint-scope: 5.1.1 eslint-utils: 2.1.0 transitivePeerDependencies: @@ -739,7 +739,7 @@ packages: - typescript dev: true - /@typescript-eslint/parser@4.19.0(eslint@7.22.0)(typescript@5.0.4): + /@typescript-eslint/parser@4.19.0(eslint@7.29.0)(typescript@5.0.4): resolution: {integrity: sha512-/uabZjo2ZZhm66rdAu21HA8nQebl3lAIDcybUoOxoI7VbZBYavLIwtOOmykKCJy+Xq6Vw6ugkiwn8Js7D6wieA==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -753,7 +753,7 @@ packages: '@typescript-eslint/types': 4.19.0 '@typescript-eslint/typescript-estree': 4.19.0(typescript@5.0.4) debug: 4.3.4 - eslint: 7.22.0 + eslint: 7.29.0 typescript: 5.0.4 transitivePeerDependencies: - supports-color @@ -1684,7 +1684,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@4.19.0)(eslint-import-resolver-node@0.3.9)(eslint@7.22.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@4.19.0)(eslint-import-resolver-node@0.3.9)(eslint@7.29.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -1705,23 +1705,23 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 4.19.0(eslint@7.22.0)(typescript@5.0.4) + '@typescript-eslint/parser': 4.19.0(eslint@7.29.0)(typescript@5.0.4) debug: 3.2.7 - eslint: 7.22.0 + eslint: 7.29.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-header@3.1.1(eslint@7.22.0): + /eslint-plugin-header@3.1.1(eslint@7.29.0): resolution: {integrity: sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==} peerDependencies: eslint: '>=7.7.0' dependencies: - eslint: 7.22.0 + eslint: 7.29.0 dev: true - /eslint-plugin-import@2.25.2(@typescript-eslint/parser@4.19.0)(eslint@7.22.0): + /eslint-plugin-import@2.25.2(@typescript-eslint/parser@4.19.0)(eslint@7.29.0): resolution: {integrity: sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g==} engines: {node: '>=4'} peerDependencies: @@ -1731,14 +1731,14 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 4.19.0(eslint@7.22.0)(typescript@5.0.4) + '@typescript-eslint/parser': 4.19.0(eslint@7.29.0)(typescript@5.0.4) array-includes: 3.1.7 array.prototype.flat: 1.3.2 debug: 2.6.9 doctrine: 2.1.0 - eslint: 7.22.0 + eslint: 7.29.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@4.19.0)(eslint-import-resolver-node@0.3.9)(eslint@7.22.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@4.19.0)(eslint-import-resolver-node@0.3.9)(eslint@7.29.0) has: 1.0.4 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -1752,16 +1752,16 @@ packages: - supports-color dev: true - /eslint-plugin-no-secrets@0.8.9(eslint@7.22.0): + /eslint-plugin-no-secrets@0.8.9(eslint@7.29.0): resolution: {integrity: sha512-CqaBxXrImABCtxMWspAnm8d5UKkpNylC7zqVveb+fJHEvsSiNGJlSWzdSIvBUnW1XhJXkzifNIZQC08rEII5Ng==} engines: {node: '>=10.0.0', npm: '>=6.9.0'} peerDependencies: eslint: '>=3.0.0' dependencies: - eslint: 7.22.0 + eslint: 7.29.0 dev: true - /eslint-plugin-prettier@4.0.0(eslint@7.22.0)(prettier@2.4.1): + /eslint-plugin-prettier@4.0.0(eslint@7.29.0)(prettier@2.4.1): resolution: {integrity: sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==} engines: {node: '>=6.0.0'} peerDependencies: @@ -1772,7 +1772,7 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 7.22.0 + eslint: 7.29.0 prettier: 2.4.1 prettier-linter-helpers: 1.0.0 dev: true @@ -1802,8 +1802,8 @@ packages: engines: {node: '>=10'} dev: true - /eslint@7.22.0: - resolution: {integrity: sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg==} + /eslint@7.29.0: + resolution: {integrity: sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==} engines: {node: ^10.12.0 || >=12.0.0} hasBin: true dependencies: @@ -1815,12 +1815,14 @@ packages: debug: 4.3.4 doctrine: 3.0.0 enquirer: 2.4.1 + escape-string-regexp: 4.0.0 eslint-scope: 5.1.1 eslint-utils: 2.1.0 eslint-visitor-keys: 2.1.0 espree: 7.3.1 esquery: 1.5.0 esutils: 2.0.3 + fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 functional-red-black-tree: 1.0.1 glob-parent: 5.1.2 @@ -1832,7 +1834,7 @@ packages: js-yaml: 3.14.1 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 - lodash: 4.17.21 + lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.3 @@ -2164,7 +2166,7 @@ packages: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.0.4 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 dev: true @@ -2842,6 +2844,10 @@ packages: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} dev: true + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + /lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} dev: true diff --git a/packages/api/review/teamsfx-api.api.md b/packages/api/review/teamsfx-api.api.md index d79d894f41..2c33c35224 100644 --- a/packages/api/review/teamsfx-api.api.md +++ b/packages/api/review/teamsfx-api.api.md @@ -392,6 +392,7 @@ export interface InputResult { // @public (undocumented) export interface Inputs extends Record { + agent?: "teams" | "office"; // (undocumented) correlationId?: string; // (undocumented) diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index 7e222ab9c3..fe13dc5d3e 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -71,6 +71,10 @@ export interface Inputs extends Record { projectId?: string; nonInteractive?: boolean; correlationId?: string; + /** + * whether the caller is triggered by @teams or @office agent + */ + agent?: "teams" | "office"; } export type InputsWithProjectPath = Inputs & { projectPath: string }; diff --git a/packages/fx-core/package.json b/packages/fx-core/package.json index 8fb29e1487..795a93bbe6 100644 --- a/packages/fx-core/package.json +++ b/packages/fx-core/package.json @@ -174,7 +174,7 @@ "chai-spies": "^1.0.0", "copy-webpack-plugin": "^6.4.1", "dotenv": "^8.2.0", - "eslint": "^7.22.0", + "eslint": "^7.29.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.25.2", "eslint-plugin-no-secrets": "^0.8.9", diff --git a/packages/fx-core/pnpm-lock.yaml b/packages/fx-core/pnpm-lock.yaml index 13f70c1ae5..9ae98ebecf 100644 --- a/packages/fx-core/pnpm-lock.yaml +++ b/packages/fx-core/pnpm-lock.yaml @@ -287,7 +287,7 @@ devDependencies: specifier: ^6.4.1 version: 6.4.1(webpack@5.62.1) eslint: - specifier: ^7.22.0 + specifier: ^7.29.0 version: 7.29.0 eslint-plugin-header: specifier: ^3.1.1 diff --git a/packages/fx-core/resource/package.nls.json b/packages/fx-core/resource/package.nls.json index c1ac6af8e4..a62d6d389e 100644 --- a/packages/fx-core/resource/package.nls.json +++ b/packages/fx-core/resource/package.nls.json @@ -1,4 +1,6 @@ { + "core.addApi.confirm":"Teams Toolkit will modify files in your \"%s\" folder based on the new OpenAPI document you provided. To avoid losing unexpected changes, back up your files or use git for change tracking before proceeding.", + "core.addApi.continue": "Add", "core.provision.provision": "Provision", "core.provision.learnMore": "More info", "core.provision.azureAccount": "Azure account: %s", @@ -218,7 +220,8 @@ "error.generator.DownloadSampleNetworkError": "Unable to download sample due to network error. Check your network connection and try again or you can manually clone the repo from %s", "error.copilotPlugin.apiSpecNotUsedInPlugin": "\"%s\" is not used in the plugin.", "error.copilotPlugin.openAiPluginManifest.CannotGetManifest": "Unable to get OpenAI plugin manifest from '%s'.", - "error.copilotPlugin.noExtraAPICanBeAdded": "Unable to add API as only GET and POST methods with at most 5 required parameter and no authentication are supported. Also, methods defined in manifest.json are not listed.", + "error.apime.noExtraAPICanBeAdded": "Unable to add API because only GET and POST methods are supported, with a maximum of 5 required parameters and no authentication. Also, methods defined in the manifest are not listed.", + "error.copilot.noExtraAPICanBeAdded": "Unable to add API because no authentication is supported. Also, methods defined in the current OpenAPI description document are not listed.", "error.m365.NotExtendedToM365Error": "Unable to extend Teams app to Microsoft 365. Use 'teamsApp/extendToM365' action to extend your Teams app to Microsoft 365.", "core.QuestionAppName.validation.pattern": "App name needs to begin with letters, include minimum two letters or digits, and exclude certain special characters.", "core.QuestionAppName.validation.maxlength": "App name is longer than the 30 characters.", @@ -346,7 +349,9 @@ "core.createProjectQuestion.llmService.azureOpenAIKey.title": "Azure OpenAI Key", "core.createProjectQuestion.llmService.azureOpenAIKey.placeholder": "Input Azure OpenAI service key now or set it later in the project", "core.createProjectQuestion.llmService.azureOpenAIEndpoint.title": "Azure OpenAI Endpoint", + "core.createProjectQuestion.llmService.azureOpenAIDeploymentName.title": "Azure OpenAI Deployment Name", "core.createProjectQuestion.llmService.azureOpenAIEndpoint.placeholder": "Input Azure OpenAI service endpoint now or set it later in the project", + "core.createProjectQuestion.llmService.azureOpenAIDeploymentName.placeholder": "Input Azure OpenAI deployment name now or set it later in the project", "core.createProjectQuestion.apiSpec.title": "OpenAPI Description Document", "core.createProjectQuestion.apiSpec.placeholder": "Enter OpenAPI Description Document URL", "core.createProjectQuestion.apiSpecInputUrl.label": "Enter OpenAPI Description Document Location", @@ -366,8 +371,8 @@ "core.createProjectQuestion.apiSpec.operation.multipleAuth": "Your selected APIs have multiple authorizations %s which are not supported.", "core.createProjectQuestion.apiSpec.operation.multipleServer": "Your selected APIs have multiple server URLs %s which are not supported.", "core.createProjectQuestion.apiSpec.operation.placeholder.skipExisting": "Methods defined in manifest.json are not listed", - "core.createProjectQuestion.apiSpec.multipleValidationErrors.message": "Invalid OpenAPI description document. Check output panel for details.", - "core.createProjectQuestion.apiSpec.multipleValidationErrors.vscode.message": "Invalid OpenAPI description document. Check [output panel](command:fx-extension.showOutputChannel) for details.", + "core.createProjectQuestion.apiSpec.multipleValidationErrors.message": "Incompatible OpenAPI description document. Check output panel for details.", + "core.createProjectQuestion.apiSpec.multipleValidationErrors.vscode.message": "Incompatible OpenAPI description document. Check [output panel](command:fx-extension.showOutputChannel) for details.", "core.createProjectQuestion.openAiPluginManifest.multipleValidationErrors.vscode.message": "Invalid OpenAI plugin manifest. Check [output panel](command:fx-extension.showOutputChannel) for details.", "core.createProjectQuestion.openAiPluginManifest.validationError.missingApiUrl": "Missing URL in \"%s\".", "core.createProjectQuestion.openAiPluginManifest.validationError.authNotSupported": "Auth type is not supported. Supported auth type: \"%s\".", @@ -444,37 +449,37 @@ "core.SampleSelect.placeholder": "Select a sample", "core.SampleSelect.buttons.viewSamples": "View samples", "core.updateBotIdsQuestion.title": "Create new bot(s) for debugging", - "core.updateBotIdsQuestion.placeholder": "Unselect to keep with the original value of botId", - "_core.updateBotIdsQuestion.placeholder.comment": "'botId' is the field name which should not be localized.", + "core.updateBotIdsQuestion.placeholder": "Deselect to keep the original botId value", + "_core.updateBotIdsQuestion.placeholder.comment": "'botId' is the field name that shouldn't be localized.", "core.updateBotIdForBot.description": "Update botId %s to \"${{BOT_ID}}\" in manifest.json", - "_core.updateBotIdForBot.description.comment": "'botId' and '${{BOT_ID}}' should not be localized. 'manifest.json' is the file name which should not be localized.", + "_core.updateBotIdForBot.description.comment": "'botId' and '${{BOT_ID}}' shouldn't be localized. 'manifest.json' is the file name that shouldn't be localized.", "core.updateBotIdForMessageExtension.description": "Update botId %s to \"${{BOT_ID}}\" in manifest.json", - "_core.updateBotIdForMessageExtension.description.comment": "'botId' and '${{BOT_ID}}' should not be localized. 'manifest.json' is the file name which should not be localized.", + "_core.updateBotIdForMessageExtension.description.comment": "'botId' and '${{BOT_ID}}' shouldn't be localized. 'manifest.json' is the file name that shouldn't be localized.", "core.updateBotIdForBot.label": "Bot", "core.updateBotIdForMessageExtension.label": "Message extension", "core.updateContentUrlQuestion.title": "Configure content URL(s) for debugging", "core.updateWebsiteUrlQuestion.title": "Configure website URL(s) for debugging", "core.updateContentUrlOption.description": "Update the content URL from %s to %s", "core.updateWebsiteUrlOption.description": "Update the website URL from %s to %s", - "core.updateUrlQuestion.placeholder": "Unselect to keep with the original URL", + "core.updateUrlQuestion.placeholder": "Deselect to keep the original URL", "core.SingleSignOnOption.label": "Single Sign-On", "core.SingleSignOnOption.detail": "Develop a Single Sign-On feature for Teams Launch pages and Bot capability", "core.getUserEmailQuestion.title": "Add owner to Teams/Microsoft Entra app for the account under the same Microsoft 365 tenant (email)", - "core.getUserEmailQuestion.validation1": "Email address cannot be null or empty", - "core.getUserEmailQuestion.validation2": "Please change [UserName] to the real user name", + "core.getUserEmailQuestion.validation1": "Enter email address", + "core.getUserEmailQuestion.validation2": "Change [UserName] to the real user name", "core.collaboration.error.failedToLoadDotEnvFile": "Unable to load your .env File. Reason: %s", "core.selectAadAppManifestQuestion.title": "Select Microsoft Entra manifest.json file", "core.selectTeamsAppManifestQuestion.title": "Select Teams manifest.json file", "core.selectTeamsAppPackageQuestion.title": "Select Teams app package file", "core.selectLocalTeamsAppManifestQuestion.title": "Select local Teams manifest.json file", - "core.selectCollaborationAppTypeQuestion.title": "Select app you want to manage the collaborators", + "core.selectCollaborationAppTypeQuestion.title": "Select the app for which you want to manage collaborators", "core.selectValidateMethodQuestion.validate.selectTitle": "Select a validation method", "core.selectValidateMethodQuestion.validate.schemaOption": "Validate using manifest schema", "core.selectValidateMethodQuestion.validate.schemaOptionDescription": "Validate using manifest schema", "core.selectValidateMethodQuestion.validate.appPackageOption": "Validate app package using validation rules", "core.selectValidateMethodQuestion.validate.appPackageOptionDescription": "Validate app package using validation rules", "core.selectValidateMethodQuestion.validate.testCasesOption": "Publish Readiness", - "core.selectValidateMethodQuestion.validate.testCasesOptionDescription": "Check your app with the test cases Microsoft uses before they publish it", + "core.selectValidateMethodQuestion.validate.testCasesOptionDescription": "Check your app with Microsoft's test cases before publishing", "core.confirmManifestQuestion.placeholder": "Confirm you've selected the correct manifest file", "core.aadAppQuestion.label": "Microsoft Entra app", "core.aadAppQuestion.description": "Your Microsoft Entra app for Single Sign On", @@ -507,10 +512,30 @@ "core.common.NoServerInformation": "No server information is found in the OpenAPI description document.", "core.common.RemoteRefNotSupported": "Remote reference is not supported: %s.", "core.common.MissingOperationId": "Missing operationIds: %s.", - "core.common.NoSupportedApi": "No supported API is found in the OpenAPI description document: only GET and POST methods are supported, additionally, there can be at most 5 required parameter, and no auth is allowed. \nFor more information visit: \"https://aka.ms/build-api-based-message-extension\".", "core.common.UrlProtocolNotSupported": "Server url is incorrect: protocol %s is not supported, use https protocol.", "core.common.RelativeServerUrlNotSupported": "Server url is incorrect: relative server url is not supported.", "core.common.ErrorFetchApiSpec": "Your OpenAPI description document should be accessible without authentication. If not, download and use a local copy.", + "core.common.NoSupportedApi": "No supported API found in the OpenAPI document.\nFor more information visit: \"https://aka.ms/build-api-based-message-extension\". \nReasons for API incompatibility are listed below:\n%s", + "core.common.NoSupportedApiCopilot": "No supported API is found in the OpenAPI description document. \nReasons for API incompatibility are listed below:\n%s", + "core.common.invalidReason.AuthTypeIsNotSupported": "authorization type is not supported", + "core.common.invalidReason.MissingOperationId": "operation id is missing", + "core.common.invalidReason.PostBodyContainMultipleMediaTypes": "post body contains multiple media types", + "core.common.invalidReason.ResponseContainMultipleMediaTypes": "response contains multiple media types", + "core.common.invalidReason.ResponseJsonIsEmpty": "response json is empty", + "core.common.invalidReason.PostBodySchemaIsNotJson": "post body schema is not json", + "core.common.invalidReason.PostBodyContainsRequiredUnsupportedSchema": "post body contains required unsupported schema", + "core.common.invalidReason.ParamsContainRequiredUnsupportedSchema": "params contain required unsupported schema", + "core.common.invalidReason.ParamsContainsNestedObject": "params contain nested object", + "core.common.invalidReason.RequestBodyContainsNestedObject": "request body contains nested object", + "core.common.invalidReason.ExceededRequiredParamsLimit": "exceeded required params limit", + "core.common.invalidReason.NoParameter": "no parameter", + "core.common.invalidReason.NoAPIInfo": "no API info", + "core.common.invalidReason.MethodNotAllowed": "method not allowed", + "core.common.invalidReason.UrlPathNotExist": "url path does not exist", + "core.common.invalidReason.NoAPIs": "No APIs were found in the OpenAPI description document.", + "core.common.UrlProtocolNotSupported": "Server url is not correct: protocol %s is not supported, you should use https protocol instead.", + "core.common.RelativeServerUrlNotSupported": "Server url is not correct: relative server url is not supported.", + "core.common.ErrorFetchApiSpec": "Your OpenAPI description document should be accessible without authentication, otherwise download and start from a local copy.", "core.common.SendingApiRequest": "Sending API request: %s. Request body: %s", "core.common.ReceiveApiResponse": "Received API response: %s.", "core.importAddin.label": "Import Existing Outlook Add-ins", @@ -571,7 +596,7 @@ "depChecker.learnMoreButtonText": "Learn more", "depChecker.needInstallNpm": "You must have NPM installed to debug your local functions.", "depChecker.failToValidateFuncCoreTool": "Unable to validate Azure Functions Core Tools after installation.", - "depChecker.symlinkDirAlreadyExist": "The destination of the symlink already exists", + "depChecker.symlinkDirAlreadyExist": "The destination of the symlink (%s) already exists, please remove it and try again.", "depChecker.portableFuncNodeNotMatched": "Your Node.js (@NodeVersion) is incompatible with Teams Toolkit Azure Functions Core Tools (@FuncVersion).", "depChecker.invalidFuncVersion": "The format of version %s is invalid.", "depChecker.noSentinelFile": "Azure Functions Core Tools installation is incomplete.", diff --git a/packages/fx-core/src/common/constants.ts b/packages/fx-core/src/common/constants.ts index eae66a0815..8e17d105aa 100644 --- a/packages/fx-core/src/common/constants.ts +++ b/packages/fx-core/src/common/constants.ts @@ -67,5 +67,4 @@ export class FeatureFlagName { static readonly TdpTemplateCliTest = "TEAMSFX_TDP_TEMPLATE_CLI_TEST"; static readonly AsyncAppValidation = "TEAMSFX_ASYNC_APP_VALIDATION"; static readonly NewProjectType = "TEAMSFX_NEW_PROJECT_TYPE"; - static readonly ApiMeSSO = "API_ME_SSO"; } diff --git a/packages/fx-core/src/common/deps-checker/constant/message.ts b/packages/fx-core/src/common/deps-checker/constant/message.ts index 9def556378..56f41f602e 100644 --- a/packages/fx-core/src/common/deps-checker/constant/message.ts +++ b/packages/fx-core/src/common/deps-checker/constant/message.ts @@ -16,7 +16,8 @@ export const Messages = { getLocalizedString("depChecker.portableFuncNodeNotMatched") .replace("@NodeVersion", nodeVersion) .replace("@FuncVersion", funcVersion), - symlinkDirAlreadyExist: () => getLocalizedString("depChecker.symlinkDirAlreadyExist"), + symlinkDirAlreadyExist: (linkFilePath: string) => + getLocalizedString("depChecker.symlinkDirAlreadyExist", linkFilePath), invalidFuncVersion: (version: string) => getLocalizedString("depChecker.invalidFuncVersion", version), noSentinelFile: () => getLocalizedString("depChecker.noSentinelFile"), diff --git a/packages/fx-core/src/common/deps-checker/util/fileHelper.ts b/packages/fx-core/src/common/deps-checker/util/fileHelper.ts index 16b6d11bcb..46e859f3e5 100644 --- a/packages/fx-core/src/common/deps-checker/util/fileHelper.ts +++ b/packages/fx-core/src/common/deps-checker/util/fileHelper.ts @@ -27,7 +27,7 @@ export async function createSymlink(target: string, linkFilePath: string): Promi await unlinkSymlink(linkFilePath); // check if destination already exists if (await fs.pathExists(linkFilePath)) { - throw new DepsCheckerError(Messages.symlinkDirAlreadyExist(), v3DefaultHelpLink); + throw new DepsCheckerError(Messages.symlinkDirAlreadyExist(linkFilePath), v3DefaultHelpLink); } return await fs.ensureSymlink( diff --git a/packages/fx-core/src/common/featureFlags.ts b/packages/fx-core/src/common/featureFlags.ts index 49eb55159b..9183d39f7e 100644 --- a/packages/fx-core/src/common/featureFlags.ts +++ b/packages/fx-core/src/common/featureFlags.ts @@ -62,10 +62,6 @@ export function isMultipleParametersEnabled(): boolean { return isFeatureFlagEnabled(FeatureFlagName.MultipleParameters, true); } -export function isOfficeXMLAddinEnabled(): boolean { - return isFeatureFlagEnabled(FeatureFlagName.OfficeXMLAddin, false); -} - export function isOfficeJSONAddinEnabled(): boolean { return isFeatureFlagEnabled(FeatureFlagName.OfficeAddin, false); } @@ -86,10 +82,6 @@ export function isNewProjectTypeEnabled(): boolean { return isFeatureFlagEnabled(FeatureFlagName.NewProjectType, true); } -export function isApiMeSSOEnabled(): boolean { - return isFeatureFlagEnabled(FeatureFlagName.ApiMeSSO, false); -} - /////////////////////////////////////////////////////////////////////////////// // Notes for Office Addin Feature flags: // Case 1: TEAMSFX_OFFICE_ADDIN = false, TEAMSFX_OFFICE_XML_ADDIN = false diff --git a/packages/fx-core/src/common/projectTypeChecker.ts b/packages/fx-core/src/common/projectTypeChecker.ts index 1ab3f04b38..a5597bf270 100644 --- a/packages/fx-core/src/common/projectTypeChecker.ts +++ b/packages/fx-core/src/common/projectTypeChecker.ts @@ -270,6 +270,9 @@ export function getCapabilities(manifest: any): string[] { if (manifest.extensions && manifest.extensions.length > 0) { capabilities.push("extension"); } + if (manifest.plugins && manifest.plugins.length > 0) { + capabilities.push("plugin"); + } return capabilities; } export const projectTypeChecker = new ProjectTypeChecker(); diff --git a/packages/fx-core/src/component/coordinator/index.ts b/packages/fx-core/src/component/coordinator/index.ts index 0fe0d2cf51..4fb69bd6ef 100644 --- a/packages/fx-core/src/component/coordinator/index.ts +++ b/packages/fx-core/src/component/coordinator/index.ts @@ -23,7 +23,6 @@ import { EOL } from "os"; import * as path from "path"; import * as uuid from "uuid"; import * as xml2js from "xml2js"; -import { isApiKeyEnabled, isApiMeSSOEnabled } from "../../common/featureFlags"; import { getLocalizedString } from "../../common/localizeUtils"; import { TelemetryEvent, TelemetryProperty } from "../../common/telemetry"; import { getResourceGroupInPortal } from "../../common/tools"; @@ -361,11 +360,7 @@ class Coordinator { capability === CapabilityOptions.m365SearchMe().id && meArchitecture === MeArchitectureOptions.newApi().id ) { - if ((isApiKeyEnabled() || isApiMeSSOEnabled()) && apiMEAuthType) { - feature = `${feature}:${apiMEAuthType}`; - } else { - feature = `${feature}:none`; - } + feature = `${feature}:${apiMEAuthType}`; } if (capability === CapabilityOptions.customCopilotRag().id) { @@ -384,6 +379,8 @@ class Coordinator { const openAIKey: string | undefined = inputs[QuestionNames.OpenAIKey]; const azureOpenAIKey: string | undefined = inputs[QuestionNames.AzureOpenAIKey]; const azureOpenAIEndpoint: string | undefined = inputs[QuestionNames.AzureOpenAIEndpoint]; + const azureOpenAIDeploymentName: string | undefined = + inputs[QuestionNames.AzureOpenAIDeploymentName]; context.templateVariables = Generator.getDefaultVariables( appName, safeProjectNameFromVS, @@ -395,6 +392,7 @@ class Coordinator { openAIKey, azureOpenAIKey, azureOpenAIEndpoint, + azureOpenAIDeploymentName, } ); const res = await Generator.generateTemplate(context, projectPath, templateName, langKey); diff --git a/packages/fx-core/src/component/driver/teamsApp/createAppPackage.ts b/packages/fx-core/src/component/driver/teamsApp/createAppPackage.ts index de9c1143df..aa15d44d43 100644 --- a/packages/fx-core/src/component/driver/teamsApp/createAppPackage.ts +++ b/packages/fx-core/src/component/driver/teamsApp/createAppPackage.ts @@ -218,8 +218,8 @@ export class CreateAppPackageDriver implements StepDriver { } // API plugin - if (manifest.plugins && manifest.plugins.length > 0 && manifest.plugins[0].pluginFile) { - const pluginFile = path.resolve(appDirectory, manifest.plugins[0].pluginFile); + if (manifest.plugins && manifest.plugins.length > 0 && manifest.plugins[0].file) { + const pluginFile = path.resolve(appDirectory, manifest.plugins[0].file); const checkExistenceRes = await this.validateReferencedFile(pluginFile, appDirectory); if (checkExistenceRes.isErr()) { return err(checkExistenceRes.error); @@ -227,7 +227,7 @@ export class CreateAppPackageDriver implements StepDriver { const addFileWithVariableRes = await this.addFileWithVariable( zip, - manifest.plugins[0].pluginFile, + manifest.plugins[0].file, pluginFile, TelemetryPropertyKey.customizedAIPluginKeys, context @@ -238,7 +238,7 @@ export class CreateAppPackageDriver implements StepDriver { const addFilesRes = await this.addPluginRelatedFiles( zip, - manifest.plugins[0].pluginFile, + manifest.plugins[0].file, appDirectory, context ); diff --git a/packages/fx-core/src/component/driver/teamsApp/utils/ManifestUtils.ts b/packages/fx-core/src/component/driver/teamsApp/utils/ManifestUtils.ts index 7244cde403..dcb6e69ebc 100644 --- a/packages/fx-core/src/component/driver/teamsApp/utils/ManifestUtils.ts +++ b/packages/fx-core/src/component/driver/teamsApp/utils/ManifestUtils.ts @@ -50,6 +50,7 @@ import { TelemetryPropertyKey } from "./telemetry"; import { WrapDriverContext } from "../../util/wrapUtil"; import { hooks } from "@feathersjs/hooks"; import { ErrorContextMW } from "../../../../core/globalVars"; +import { getCapabilities as checkManifestCapabilities } from "../../../../common/projectTypeChecker"; export class ManifestUtils { async readAppManifest(projectPath: string): Promise> { @@ -252,20 +253,7 @@ export class ManifestUtils { } } public getCapabilities(template: TeamsAppManifest): string[] { - const capabilities: string[] = []; - if (template.staticTabs && template.staticTabs.length > 0) { - capabilities.push("staticTab"); - } - if (template.configurableTabs && template.configurableTabs.length > 0) { - capabilities.push("configurableTab"); - } - if (template.bots && template.bots.length > 0) { - capabilities.push("Bot"); - } - if (template.composeExtensions) { - capabilities.push("MessageExtension"); - } - return capabilities; + return checkManifestCapabilities(template); } /** @@ -286,7 +274,7 @@ export class ManifestUtils { manifest: TeamsAppManifest, manifestPath: string ): Promise> { - const pluginFile = manifest.plugins?.[0]?.pluginFile; + const pluginFile = manifest.plugins?.[0]?.file; if (pluginFile) { const plugin = path.resolve(path.dirname(manifestPath), pluginFile); const doesFileExist = await fs.pathExists(plugin); diff --git a/packages/fx-core/src/component/generator/copilotPlugin/helper.ts b/packages/fx-core/src/component/generator/copilotPlugin/helper.ts index e8aa2b1fde..9781a9c865 100644 --- a/packages/fx-core/src/component/generator/copilotPlugin/helper.ts +++ b/packages/fx-core/src/component/generator/copilotPlugin/helper.ts @@ -40,6 +40,7 @@ import { ParseOptions, AdaptiveCardGenerator, Utils, + InvalidAPIInfo, } from "@microsoft/m365-spec-parser"; import fs from "fs-extra"; import { getLocalizedString } from "../../../common/localizeUtils"; @@ -80,10 +81,10 @@ enum OpenAIPluginManifestErrorType { } export const copilotPluginParserOptions: ParseOptions = { - allowAPIKeyAuth: true, - allowBearerTokenAuth: true, + allowAPIKeyAuth: false, + allowBearerTokenAuth: false, allowMultipleParameters: true, - allowOauth2: true, + allowOauth2: false, projectType: ProjectType.Copilot, allowMissingId: true, allowSwagger: true, @@ -201,7 +202,7 @@ export async function listOperations( } ); const validationRes = await specParser.validate(); - validationRes.errors = formatValidationErrors(validationRes.errors); + validationRes.errors = formatValidationErrors(validationRes.errors, inputs); logValidationResults( validationRes.errors, @@ -251,12 +252,15 @@ export async function listOperations( ); // No extra API can be added if (operations.length == 0) { - const errors = [ - { - type: ApiSpecErrorType.NoExtraAPICanBeAdded, - content: getLocalizedString("error.copilotPlugin.noExtraAPICanBeAdded"), - }, - ]; + const errors = formatValidationErrors( + [ + { + type: ApiSpecErrorType.NoExtraAPICanBeAdded, + content: "", + }, + ], + inputs + ); logValidationResults(errors, [], context, true, false, false, existingCorrelationId); return err(errors); } @@ -307,7 +311,7 @@ function sortOperations(operations: ListAPIInfo[]): ApiOperation[] { } function formatTelemetryValidationProperty(result: ErrorResult | WarningResult): string { - return result.type.toString() + ": " + result.content; + return result.type.toString(); } export async function listPluginExistingOperations( @@ -668,17 +672,60 @@ export async function isYamlSpecFile(specPath: string): Promise { } } -export function formatValidationErrors(errors: ApiSpecErrorResult[]): ApiSpecErrorResult[] { +export function formatValidationErrors( + errors: ApiSpecErrorResult[], + inputs: Inputs +): ApiSpecErrorResult[] { return errors.map((error) => { return { type: error.type, - content: formatValidationErrorContent(error), + content: formatValidationErrorContent(error, inputs), data: error.data, }; }); } -function formatValidationErrorContent(error: ApiSpecErrorResult): string { +function mapInvalidReasonToMessage(reason: ErrorType): string { + switch (reason) { + case ErrorType.AuthTypeIsNotSupported: + return getLocalizedString("core.common.invalidReason.AuthTypeIsNotSupported"); + case ErrorType.MissingOperationId: + return getLocalizedString("core.common.invalidReason.MissingOperationId"); + case ErrorType.PostBodyContainMultipleMediaTypes: + return getLocalizedString("core.common.invalidReason.PostBodyContainMultipleMediaTypes"); + case ErrorType.ResponseContainMultipleMediaTypes: + return getLocalizedString("core.common.invalidReason.ResponseContainMultipleMediaTypes"); + case ErrorType.ResponseJsonIsEmpty: + return getLocalizedString("core.common.invalidReason.ResponseJsonIsEmpty"); + case ErrorType.PostBodySchemaIsNotJson: + return getLocalizedString("core.common.invalidReason.PostBodySchemaIsNotJson"); + case ErrorType.PostBodyContainsRequiredUnsupportedSchema: + return getLocalizedString( + "core.common.invalidReason.PostBodyContainsRequiredUnsupportedSchema" + ); + case ErrorType.ParamsContainRequiredUnsupportedSchema: + return getLocalizedString("core.common.invalidReason.ParamsContainRequiredUnsupportedSchema"); + case ErrorType.ParamsContainsNestedObject: + return getLocalizedString("core.common.invalidReason.ParamsContainsNestedObject"); + case ErrorType.RequestBodyContainsNestedObject: + return getLocalizedString("core.common.invalidReason.RequestBodyContainsNestedObject"); + case ErrorType.ExceededRequiredParamsLimit: + return getLocalizedString("core.common.invalidReason.ExceededRequiredParamsLimit"); + case ErrorType.NoParameter: + return getLocalizedString("core.common.invalidReason.NoParameter"); + case ErrorType.NoAPIInfo: + return getLocalizedString("core.common.invalidReason.NoAPIInfo"); + case ErrorType.MethodNotAllowed: + return getLocalizedString("core.common.invalidReason.MethodNotAllowed"); + case ErrorType.UrlPathNotExist: + return getLocalizedString("core.common.invalidReason.UrlPathNotExist"); + default: + return reason.toString(); + } +} + +function formatValidationErrorContent(error: ApiSpecErrorResult, inputs: Inputs): string { + const isPlugin = inputs[QuestionNames.Capabilities] === copilotPluginApiSpecOptionId; try { switch (error.type) { case ErrorType.SpecNotValid: { @@ -702,9 +749,23 @@ function formatValidationErrorContent(error: ApiSpecErrorResult): string { case ErrorType.RelativeServerUrlNotSupported: return getLocalizedString("core.common.RelativeServerUrlNotSupported"); case ErrorType.NoSupportedApi: - return getLocalizedString("core.common.NoSupportedApi"); + const messages = []; + const invalidAPIInfo = error.data as InvalidAPIInfo[]; + for (const info of invalidAPIInfo) { + const mes = `${info.api}: ${info.reason.map(mapInvalidReasonToMessage).join(", ")}`; + messages.push(mes); + } + + if (messages.length === 0) { + messages.push(getLocalizedString("core.common.invalidReason.NoAPIs")); + } + return isPlugin + ? getLocalizedString("core.common.NoSupportedApiCopilot", messages.join("\n")) + : getLocalizedString("core.common.NoSupportedApi", messages.join("\n")); case ErrorType.NoExtraAPICanBeAdded: - return getLocalizedString("error.copilotPlugin.noExtraAPICanBeAdded"); + return isPlugin + ? getLocalizedString("error.copilot.noExtraAPICanBeAdded") + : getLocalizedString("error.apime.noExtraAPICanBeAdded"); case ErrorType.ResolveServerUrlFailed: return error.content; case ErrorType.Cancelled: @@ -773,7 +834,7 @@ async function updatePromptForCustomApi( const promptFilePath = path.join(chatFolder, "skprompt.txt"); const prompt = `The following is a conversation with an AI assistant.\nThe assistant can help to call APIs for the open api spec file${ spec.info.description ? ". " + spec.info.description : "." - }\n\ncontext:\nAvailable actions: {{getAction}}.`; + }\nIf the API doesn't require parameters, invoke it with default JSON object { "path": null, "body": null, "query": null }.\n\ncontext:\nAvailable actions: {{getAction}}.`; await fs.writeFile(promptFilePath, prompt, { encoding: "utf-8", flag: "w" }); } } diff --git a/packages/fx-core/src/component/generator/generator.ts b/packages/fx-core/src/component/generator/generator.ts index 2e69e510c5..30d73ac14e 100644 --- a/packages/fx-core/src/component/generator/generator.ts +++ b/packages/fx-core/src/component/generator/generator.ts @@ -54,6 +54,7 @@ export class Generator { openAIKey?: string; azureOpenAIKey?: string; azureOpenAIEndpoint?: string; + azureOpenAIDeploymentName?: string; } ): { [key: string]: string } { const safeProjectName = safeProjectNameFromVS ?? convertToAlphanumericOnly(appName); @@ -79,6 +80,7 @@ export class Generator { openAIKey: llmServiceData?.openAIKey ?? "", azureOpenAIKey: llmServiceData?.azureOpenAIKey ?? "", azureOpenAIEndpoint: llmServiceData?.azureOpenAIEndpoint ?? "", + azureOpenAIDeploymentName: llmServiceData?.azureOpenAIDeploymentName ?? "", isNewProjectTypeEnabled: isNewProjectTypeEnabled() ? "true" : "", NewProjectTypeName: process.env.TEAMSFX_NEW_PROJECT_TYPE_NAME ?? "TeamsApp", NewProjectTypeExt: process.env.TEAMSFX_NEW_PROJECT_TYPE_EXTENSION ?? "ttkproj", diff --git a/packages/fx-core/src/core/FxCore.ts b/packages/fx-core/src/core/FxCore.ts index 1732a1d043..593a8a40ce 100644 --- a/packages/fx-core/src/core/FxCore.ts +++ b/packages/fx-core/src/core/FxCore.ts @@ -105,6 +105,7 @@ import { MissingRequiredInputError, MultipleAuthError, MultipleServerError, + UserCancelError, assembleError, } from "../error/common"; import { NoNeedUpgradeError } from "../error/upgrade"; @@ -1261,6 +1262,19 @@ export class FxCore { return err(manifestRes.error); } + const confirmRes = await context.userInteraction.showMessage( + "warn", + getLocalizedString("core.addApi.confirm", AppPackageFolderName), + true, + getLocalizedString("core.addApi.continue") + ); + + if (confirmRes.isErr()) { + return err(confirmRes.error); + } else if (confirmRes.value !== getLocalizedString("core.addApi.continue")) { + return err(new UserCancelError()); + } + // Merge existing operations in manifest.json const specParser = new SpecParser( url, diff --git a/packages/fx-core/src/index.ts b/packages/fx-core/src/index.ts index 679c1f3741..cd005cd25f 100644 --- a/packages/fx-core/src/index.ts +++ b/packages/fx-core/src/index.ts @@ -46,3 +46,4 @@ export * from "./ui/validationUtils"; export * from "./question"; export * from "./component/generator/copilotPlugin/helper"; export * from "./question/util"; +export * from "./common/projectTypeChecker"; diff --git a/packages/fx-core/src/question/create.ts b/packages/fx-core/src/question/create.ts index 7b35e5985e..eef8a9497c 100644 --- a/packages/fx-core/src/question/create.ts +++ b/packages/fx-core/src/question/create.ts @@ -29,9 +29,7 @@ import { isCLIDotNetEnabled, isCopilotPluginEnabled, isOfficeJSONAddinEnabled, - isOfficeXMLAddinEnabled, isTdpTemplateCliTestEnabled, - isApiMeSSOEnabled, } from "../common/featureFlags"; import { getLocalizedString } from "../common/localizeUtils"; import { sampleProvider } from "../common/samples"; @@ -180,7 +178,7 @@ export class ProjectTypeOptions { } } -function projectTypeQuestion(): SingleSelectQuestion { +export function projectTypeQuestion(): SingleSelectQuestion { const staticOptions: StaticOptions = [ ProjectTypeOptions.bot(Platform.CLI), ProjectTypeOptions.tab(Platform.CLI), @@ -213,13 +211,16 @@ function projectTypeQuestion(): SingleSelectQuestion { return [projectType]; } } else { - staticOptions.push( - isOfficeXMLAddinEnabled() && !isOfficeJSONAddinEnabled() - ? ProjectTypeOptions.officeXMLAddin(inputs.platform) - : isOfficeJSONAddinEnabled() - ? ProjectTypeOptions.officeAddin(inputs.platform) - : ProjectTypeOptions.outlookAddin(inputs.platform) - ); + if (inputs.agent === "office") { + //only for @office agent, officeXMLAddin are supported + staticOptions.push(ProjectTypeOptions.officeXMLAddin(inputs.platform)); + } else { + if (isOfficeJSONAddinEnabled()) { + staticOptions.push(ProjectTypeOptions.officeAddin(inputs.platform)); + } else { + staticOptions.push(ProjectTypeOptions.outlookAddin(inputs.platform)); + } + } } return staticOptions; }, @@ -644,17 +645,6 @@ export class CapabilityOptions { capabilityOptions.push( ...CapabilityOptions.officeAddinDynamicCapabilities(inputs?.projectType, inputs?.host) ); - // if (isOfficeXMLAddinEnabled()) { - // capabilityOptions.push( - // ...[ - // ...CapabilityOptions.officeAddinStaticCapabilities("word"), - // ...CapabilityOptions.officeAddinStaticCapabilities("excel"), - // ...CapabilityOptions.officeAddinStaticCapabilities("powerpoint"), - // ] - // ); - // } else { - // capabilityOptions.push(...CapabilityOptions.officeAddinStaticCapabilities("json")); - // } return capabilityOptions; } @@ -1461,7 +1451,15 @@ export function getLanguageOptions(inputs: Inputs): OptionItem[] { if (capabilities === CapabilityOptions.SPFxTab().id) { // SPFx only supports typescript return [{ id: ProgrammingLanguage.TS, label: "TypeScript" }]; - } else if (capabilitiesHavePythonOption.includes(capabilities)) { + } else if ( + capabilitiesHavePythonOption.includes(capabilities) && + !( + capabilities == CapabilityOptions.customCopilotRag().id && + (inputs[CapabilityOptions.customCopilotRag().id] == + CustomCopilotRagOptions.microsoft365().id || + inputs[CapabilityOptions.customCopilotRag().id] == CustomCopilotRagOptions.customApi().id) + ) + ) { // support python language return [ { id: ProgrammingLanguage.JS, label: "JavaScript" }, @@ -2045,16 +2043,7 @@ export function apiMessageExtensionAuthQuestion(): SingleSelectQuestion { ), cliDescription: "The authentication type for the API.", staticOptions: ApiMessageExtensionAuthOptions.all(), - dynamicOptions: () => { - const options: OptionItem[] = [ApiMessageExtensionAuthOptions.none()]; - if (isApiKeyEnabled()) { - options.push(ApiMessageExtensionAuthOptions.apiKey()); - } - if (isApiMeSSOEnabled()) { - options.push(ApiMessageExtensionAuthOptions.microsoftEntra()); - } - return options; - }, + dynamicOptions: () => ApiMessageExtensionAuthOptions.all(), default: ApiMessageExtensionAuthOptions.none().id, }; } @@ -2210,8 +2199,8 @@ export class CustomCopilotRagOptions { return [ CustomCopilotRagOptions.customize(), CustomCopilotRagOptions.azureAISearch(), - // CustomCopilotRagOptions.customApi(), - // CustomCopilotRagOptions.microsoft365(), + CustomCopilotRagOptions.customApi(), + CustomCopilotRagOptions.microsoft365(), ]; } } @@ -2350,6 +2339,19 @@ function azureOpenAIEndpointQuestion(): TextInputQuestion { }; } +function azureOpenAIDeploymentNameQuestion(): TextInputQuestion { + return { + type: "text", + name: QuestionNames.AzureOpenAIDeploymentName, + title: getLocalizedString( + "core.createProjectQuestion.llmService.azureOpenAIDeploymentName.title" + ), + placeholder: getLocalizedString( + "core.createProjectQuestion.llmService.azureOpenAIDeploymentName.placeholder" + ), + }; +} + export function capabilitySubTree(): IQTreeNode { const node: IQTreeNode = { data: capabilityQuestion(), @@ -2443,10 +2445,7 @@ export function capabilitySubTree(): IQTreeNode { }, { condition: (inputs: Inputs) => { - return ( - (isApiKeyEnabled() || isApiMeSSOEnabled()) && - inputs[QuestionNames.MeArchitectureType] == MeArchitectureOptions.newApi().id - ); + return inputs[QuestionNames.MeArchitectureType] == MeArchitectureOptions.newApi().id; }, data: apiMessageExtensionAuthQuestion(), }, @@ -2516,6 +2515,14 @@ export function capabilitySubTree(): IQTreeNode { return inputs[QuestionNames.AzureOpenAIKey]?.length > 0; }, data: azureOpenAIEndpointQuestion(), + children: [ + { + condition: (inputs: Inputs) => { + return inputs[QuestionNames.AzureOpenAIEndpoint]?.length > 0; + }, + data: azureOpenAIDeploymentNameQuestion(), + }, + ], }, ], }, diff --git a/packages/fx-core/src/question/inputs/CreateProjectInputs.ts b/packages/fx-core/src/question/inputs/CreateProjectInputs.ts index 9d1537205e..765fa6a226 100644 --- a/packages/fx-core/src/question/inputs/CreateProjectInputs.ts +++ b/packages/fx-core/src/question/inputs/CreateProjectInputs.ts @@ -87,7 +87,11 @@ export interface CreateProjectInputs extends Inputs { /** @description Authentication Type */ "api-me-auth"?: "none" | "api-key" | "microsoft-entra"; /** @description Chat With Your Data */ - "custom-copilot-rag"?: "custom-copilot-rag-customize" | "custom-copilot-rag-azureAISearch"; + "custom-copilot-rag"?: + | "custom-copilot-rag-customize" + | "custom-copilot-rag-azureAISearch" + | "custom-copilot-rag-customApi" + | "custom-copilot-rag-microsoft365"; /** @description AI Agent */ "custom-copilot-agent"?: "custom-copilot-agent-new" | "custom-copilot-agent-assistants-api"; /** @description Programming Language */ @@ -98,6 +102,8 @@ export interface CreateProjectInputs extends Inputs { "azure-openai-key"?: string; /** @description Azure OpenAI Endpoint */ "azure-openai-endpoint"?: string; + /** @description Azure OpenAI Deployment Name */ + "azure-openai-deployment-name"?: string; /** @description OpenAI Key */ "openai-key"?: string; /** @description Framework */ diff --git a/packages/fx-core/src/question/options/CreateProjectOptions.ts b/packages/fx-core/src/question/options/CreateProjectOptions.ts index 63a141b85a..bb22318208 100644 --- a/packages/fx-core/src/question/options/CreateProjectOptions.ts +++ b/packages/fx-core/src/question/options/CreateProjectOptions.ts @@ -152,7 +152,12 @@ export const CreateProjectOptions: CLICommandOption[] = [ type: "string", description: "Chat With Your Data", default: "custom-copilot-rag-customize", - choices: ["custom-copilot-rag-customize", "custom-copilot-rag-azureAISearch"], + choices: [ + "custom-copilot-rag-customize", + "custom-copilot-rag-azureAISearch", + "custom-copilot-rag-customApi", + "custom-copilot-rag-microsoft365", + ], }, { name: "custom-copilot-agent", @@ -186,6 +191,11 @@ export const CreateProjectOptions: CLICommandOption[] = [ type: "string", description: "Azure OpenAI Endpoint", }, + { + name: "azure-openai-deployment-name", + type: "string", + description: "Azure OpenAI Deployment Name", + }, { name: "openai-key", type: "string", diff --git a/packages/fx-core/src/question/questionNames.ts b/packages/fx-core/src/question/questionNames.ts index 2dfbff1d05..777c613be0 100644 --- a/packages/fx-core/src/question/questionNames.ts +++ b/packages/fx-core/src/question/questionNames.ts @@ -47,6 +47,7 @@ export enum QuestionNames { OpenAIKey = "openai-key", AzureOpenAIKey = "azure-openai-key", AzureOpenAIEndpoint = "azure-openai-endpoint", + AzureOpenAIDeploymentName = "azure-openai-deployment-name", Features = "features", Env = "env", diff --git a/packages/fx-core/tests/common/projectTypeChecker.test.ts b/packages/fx-core/tests/common/projectTypeChecker.test.ts index f9fbdf491f..b6e7a4b6bf 100644 --- a/packages/fx-core/tests/common/projectTypeChecker.test.ts +++ b/packages/fx-core/tests/common/projectTypeChecker.test.ts @@ -108,6 +108,7 @@ describe("ProjectTypeChecker", () => { bots: [1], composeExtensions: [1], extensions: [1], + plugins: [1], }; const capabilities = getCapabilities(manifest); assert.deepEqual(capabilities, [ @@ -116,6 +117,7 @@ describe("ProjectTypeChecker", () => { "bot", "composeExtension", "extension", + "plugin", ]); }); it("empty manifest", async () => { diff --git a/packages/fx-core/tests/component/coordinator/coordinator.create.test.ts b/packages/fx-core/tests/component/coordinator/coordinator.create.test.ts index 85435ce1c8..36f357dfb9 100644 --- a/packages/fx-core/tests/component/coordinator/coordinator.create.test.ts +++ b/packages/fx-core/tests/component/coordinator/coordinator.create.test.ts @@ -37,7 +37,7 @@ import { OfficeXMLAddinGenerator } from "../../../src/component/generator/office const V3Version = MetadataV3.projectVersion; describe("coordinator create", () => { - let mockedEnvRestore: RestoreFn = () => {}; + const mockedEnvRestore: RestoreFn = () => {}; const sandbox = sinon.createSandbox(); const tools = new MockTools(); setTools(tools); @@ -659,30 +659,7 @@ describe("coordinator create", () => { assert.equal(generator.args[0][2], TemplateNames.Tab); }); - it("create API ME (without api auth options) from new api sucessfully", async () => { - const v3ctx = createContextV3(); - v3ctx.userInteraction = new MockedUserInteraction(); - - const generator = sandbox.stub(Generator, "generateTemplate").resolves(ok(undefined)); - - const inputs: Inputs = { - platform: Platform.VSCode, - folder: ".", - [QuestionNames.ProjectType]: ProjectTypeOptions.me().id, - [QuestionNames.Capabilities]: CapabilityOptions.m365SearchMe().id, - [QuestionNames.MeArchitectureType]: MeArchitectureOptions.newApi().id, - [QuestionNames.AppName]: randomAppName(), - [QuestionNames.Scratch]: ScratchOptions.yes().id, - }; - const res = await coordinator.create(v3ctx, inputs); - assert.isTrue(res.isOk()); - assert.equal(generator.args[0][2], TemplateNames.CopilotPluginFromScratch); - }); - it("create API ME (no auth) from new api sucessfully", async () => { - mockedEnvRestore = mockedEnv({ - API_COPILOT_API_KEY: "true", - }); const v3ctx = createContextV3(); v3ctx.userInteraction = new MockedUserInteraction(); @@ -704,9 +681,6 @@ describe("coordinator create", () => { }); it("create API ME (key auth) from new api sucessfully", async () => { - mockedEnvRestore = mockedEnv({ - API_COPILOT_API_KEY: "true", - }); const v3ctx = createContextV3(); v3ctx.userInteraction = new MockedUserInteraction(); diff --git a/packages/fx-core/tests/component/driver/teamsApp/createAppPackage.test.ts b/packages/fx-core/tests/component/driver/teamsApp/createAppPackage.test.ts index 867b14dd0e..4a58b21ede 100644 --- a/packages/fx-core/tests/component/driver/teamsApp/createAppPackage.test.ts +++ b/packages/fx-core/tests/component/driver/teamsApp/createAppPackage.test.ts @@ -214,7 +214,8 @@ describe("teamsApp/createAppPackage", async () => { const manifest = new TeamsAppManifest(); manifest.plugins = [ { - pluginFile: "plugin.json", + file: "plugin.json", + id: "plugin1", }, ]; manifest.icons = { @@ -250,7 +251,8 @@ describe("teamsApp/createAppPackage", async () => { }; manifest.plugins = [ { - pluginFile: "resources/ai-plugin.json", + file: "resources/ai-plugin.json", + id: "plugin1", }, ]; sinon.stub(manifestUtils, "getManifestV3").resolves(ok(manifest)); @@ -301,7 +303,8 @@ describe("teamsApp/createAppPackage", async () => { }; manifest.plugins = [ { - pluginFile: "resources/ai-plugin.json", + file: "resources/ai-plugin.json", + id: "plugin1", }, ]; sinon.stub(manifestUtils, "getManifestV3").resolves(ok(manifest)); @@ -338,7 +341,8 @@ describe("teamsApp/createAppPackage", async () => { const manifest = new TeamsAppManifest(); manifest.plugins = [ { - pluginFile: "resources/ai-plugin.json", + file: "resources/ai-plugin.json", + id: "plugin1", }, ]; manifest.icons = { @@ -369,7 +373,8 @@ describe("teamsApp/createAppPackage", async () => { const manifest = new TeamsAppManifest(); manifest.plugins = [ { - pluginFile: "resources/ai-plugin.json", + file: "resources/ai-plugin.json", + id: "plugin1", }, ]; manifest.icons = { @@ -694,7 +699,8 @@ describe("teamsApp/createAppPackage", async () => { const manifest = new TeamsAppManifest(); manifest.plugins = [ { - pluginFile: "resources/ai-plugin.json", + file: "resources/ai-plugin.json", + id: "plugin1", }, ]; manifest.icons = { diff --git a/packages/fx-core/tests/component/driver/teamsApp/pluginManifestUtils.test.ts b/packages/fx-core/tests/component/driver/teamsApp/pluginManifestUtils.test.ts index 7d7ebdcdd0..b594f77fbe 100644 --- a/packages/fx-core/tests/component/driver/teamsApp/pluginManifestUtils.test.ts +++ b/packages/fx-core/tests/component/driver/teamsApp/pluginManifestUtils.test.ts @@ -9,6 +9,7 @@ import { pluginManifestUtils } from "../../../../src/component/driver/teamsApp/u import { PluginManifestSchema, TeamsAppManifest, ok } from "@microsoft/teamsfx-api"; import { FileNotFoundError, JSONSyntaxError } from "../../../../src"; import path from "path"; +import { AppStudioError } from "../../../../src/component/driver/teamsApp/errors"; describe("pluginManifestUtils", () => { const sandbox = sinon.createSandbox(); @@ -73,7 +74,8 @@ describe("pluginManifestUtils", () => { validDomains: [], plugins: [ { - pluginFile: "resources/plugin.json", + file: "resources/plugin.json", + id: "plugin1", }, ], }; @@ -140,6 +142,62 @@ describe("pluginManifestUtils", () => { } }); + it("getApiSpecFilePathFromTeamsManifest error: invalid plugin node case 1", async () => { + const testManifest = { + ...teamsManifest, + plugins: [], + }; + sandbox.stub(fs, "readFile").resolves(JSON.stringify(pluginManifest) as any); + const res = await pluginManifestUtils.getApiSpecFilePathFromTeamsManifest( + testManifest, + "/test/path" + ); + chai.assert.isTrue(res.isErr()); + + if (res.isErr()) { + chai.assert.equal(res.error.name, AppStudioError.TeamsAppRequiredPropertyMissingError.name); + } + }); + + it("getApiSpecFilePathFromTeamsManifest error: invalid plugin node case 2", async () => { + const testManifest = { + $schema: + "https://developer.microsoft.com/en-us/json-schemas/teams/v1.9/MicrosoftTeams.schema.json", + manifestVersion: "1.9", + version: "1.0.0", + id: "test", + packageName: "test", + developer: { + name: "test", + websiteUrl: "https://test.com", + privacyUrl: "https://test.com/privacy", + termsOfUseUrl: "https://test.com/termsofuse", + }, + icons: { + color: "icon-color.png", + outline: "icon-outline.png", + }, + name: { + short: "test", + full: "test", + }, + description: { + short: "test", + full: "test", + }, + }; + sandbox.stub(fs, "readFile").resolves(JSON.stringify(pluginManifest) as any); + const res = await pluginManifestUtils.getApiSpecFilePathFromTeamsManifest( + testManifest as unknown as TeamsAppManifest, + "/test/path" + ); + chai.assert.isTrue(res.isErr()); + + if (res.isErr()) { + chai.assert.equal(res.error.name, AppStudioError.TeamsAppRequiredPropertyMissingError.name); + } + }); + it("getApiSpecFilePathFromTeamsManifest error: spec file not exist", async () => { sandbox.stub(fs, "pathExists").callsFake(async (testPath) => { if (testPath === path.resolve("/test/resources/openapi.yaml")) { diff --git a/packages/fx-core/tests/component/generator/copilotPluginGenerator.test.ts b/packages/fx-core/tests/component/generator/copilotPluginGenerator.test.ts index e499ba3719..ef2dabf6ba 100644 --- a/packages/fx-core/tests/component/generator/copilotPluginGenerator.test.ts +++ b/packages/fx-core/tests/component/generator/copilotPluginGenerator.test.ts @@ -35,7 +35,12 @@ import { import { CopilotPluginGenerator } from "../../../src/component/generator/copilotPlugin/generator"; import { assert, expect } from "chai"; import { createContextV3 } from "../../../src/component/utils"; -import { CapabilityOptions, ProgrammingLanguage, QuestionNames } from "../../../src/question"; +import { + CapabilityOptions, + copilotPluginApiSpecOptionId, + ProgrammingLanguage, + QuestionNames, +} from "../../../src/question"; import { generateScaffoldingSummary, OpenAIPluginManifestHelper, @@ -957,6 +962,39 @@ describe("formatValidationErrors", () => { { type: ErrorType.NoSupportedApi, content: "test", + data: [], + }, + { + type: ErrorType.NoSupportedApi, + content: "test", + data: [ + { + api: "GET /api", + reason: [ + ErrorType.AuthTypeIsNotSupported, + ErrorType.MissingOperationId, + ErrorType.PostBodyContainMultipleMediaTypes, + ErrorType.ResponseContainMultipleMediaTypes, + ErrorType.ResponseJsonIsEmpty, + ErrorType.PostBodySchemaIsNotJson, + ErrorType.MethodNotAllowed, + ErrorType.UrlPathNotExist, + ], + }, + { + api: "GET /api2", + reason: [ + ErrorType.PostBodyContainsRequiredUnsupportedSchema, + ErrorType.ParamsContainRequiredUnsupportedSchema, + ErrorType.ParamsContainsNestedObject, + ErrorType.RequestBodyContainsNestedObject, + ErrorType.ExceededRequiredParamsLimit, + ErrorType.NoParameter, + ErrorType.NoAPIInfo, + ], + }, + { api: "GET /api3", reason: ["unknown"] }, + ], }, { type: ErrorType.NoExtraAPICanBeAdded, @@ -985,7 +1023,10 @@ describe("formatValidationErrors", () => { }, ]; - const res = formatValidationErrors(errors); + const res = formatValidationErrors(errors, { + platform: Platform.VSCode, + [QuestionNames.ManifestPath]: "testmanifest.json", + }); expect(res[0].content).equals("test"); expect(res[1].content).includes(getLocalizedString("core.common.ErrorFetchApiSpec")); @@ -995,15 +1036,133 @@ describe("formatValidationErrors", () => { getLocalizedString("core.common.UrlProtocolNotSupported", "http") ); expect(res[5].content).equals(getLocalizedString("core.common.RelativeServerUrlNotSupported")); - expect(res[6].content).equals(getLocalizedString("core.common.NoSupportedApi")); - expect(res[7].content).equals(getLocalizedString("error.copilotPlugin.noExtraAPICanBeAdded")); - expect(res[8].content).equals("resolveurl"); - expect(res[9].content).equals(getLocalizedString("core.common.CancelledMessage")); - expect(res[10].content).equals(getLocalizedString("core.common.SwaggerNotSupported")); - expect(res[11].content).equals( - format(getLocalizedString("core.common.SpecVersionNotSupported"), res[11].data) + expect(res[6].content).equals( + getLocalizedString( + "core.common.NoSupportedApi", + getLocalizedString("core.common.invalidReason.NoAPIs") + ) + ); + + const errorMessage1 = [ + getLocalizedString("core.common.invalidReason.AuthTypeIsNotSupported"), + getLocalizedString("core.common.invalidReason.MissingOperationId"), + getLocalizedString("core.common.invalidReason.PostBodyContainMultipleMediaTypes"), + getLocalizedString("core.common.invalidReason.ResponseContainMultipleMediaTypes"), + getLocalizedString("core.common.invalidReason.ResponseJsonIsEmpty"), + getLocalizedString("core.common.invalidReason.PostBodySchemaIsNotJson"), + getLocalizedString("core.common.invalidReason.MethodNotAllowed"), + getLocalizedString("core.common.invalidReason.UrlPathNotExist"), + ]; + const errorMessage2 = [ + getLocalizedString("core.common.invalidReason.PostBodyContainsRequiredUnsupportedSchema"), + getLocalizedString("core.common.invalidReason.ParamsContainRequiredUnsupportedSchema"), + getLocalizedString("core.common.invalidReason.ParamsContainsNestedObject"), + getLocalizedString("core.common.invalidReason.RequestBodyContainsNestedObject"), + getLocalizedString("core.common.invalidReason.ExceededRequiredParamsLimit"), + getLocalizedString("core.common.invalidReason.NoParameter"), + getLocalizedString("core.common.invalidReason.NoAPIInfo"), + ]; + + expect(res[7].content).equals( + getLocalizedString( + "core.common.NoSupportedApi", + "GET /api: " + + errorMessage1.join(", ") + + "\n" + + "GET /api2: " + + errorMessage2.join(", ") + + "\n" + + "GET /api3: unknown" + ) + ); + expect(res[8].content).equals(getLocalizedString("error.apime.noExtraAPICanBeAdded")); + expect(res[9].content).equals("resolveurl"); + expect(res[10].content).equals(getLocalizedString("core.common.CancelledMessage")); + expect(res[11].content).equals(getLocalizedString("core.common.SwaggerNotSupported")); + expect(res[12].content).equals( + format(getLocalizedString("core.common.SpecVersionNotSupported"), res[12].data) + ); + expect(res[13].content).equals("unknown"); + }); + + it("format validation errors from spec parser: copilot", () => { + const errors: ErrorResult[] = [ + { + type: ErrorType.NoSupportedApi, + content: "test", + data: [ + { + api: "GET /api", + reason: [ + ErrorType.AuthTypeIsNotSupported, + ErrorType.MissingOperationId, + ErrorType.PostBodyContainMultipleMediaTypes, + ErrorType.ResponseContainMultipleMediaTypes, + ErrorType.ResponseJsonIsEmpty, + ErrorType.PostBodySchemaIsNotJson, + ErrorType.MethodNotAllowed, + ErrorType.UrlPathNotExist, + ], + }, + { + api: "GET /api2", + reason: [ + ErrorType.PostBodyContainsRequiredUnsupportedSchema, + ErrorType.ParamsContainRequiredUnsupportedSchema, + ErrorType.ParamsContainsNestedObject, + ErrorType.RequestBodyContainsNestedObject, + ErrorType.ExceededRequiredParamsLimit, + ErrorType.NoParameter, + ErrorType.NoAPIInfo, + ], + }, + { api: "GET /api3", reason: ["unknown"] }, + ], + }, + { + type: ErrorType.NoExtraAPICanBeAdded, + content: "test", + }, + ]; + + const res = formatValidationErrors(errors, { + platform: Platform.VSCode, + [QuestionNames.Capabilities]: copilotPluginApiSpecOptionId, + }); + + const errorMessage1 = [ + getLocalizedString("core.common.invalidReason.AuthTypeIsNotSupported"), + getLocalizedString("core.common.invalidReason.MissingOperationId"), + getLocalizedString("core.common.invalidReason.PostBodyContainMultipleMediaTypes"), + getLocalizedString("core.common.invalidReason.ResponseContainMultipleMediaTypes"), + getLocalizedString("core.common.invalidReason.ResponseJsonIsEmpty"), + getLocalizedString("core.common.invalidReason.PostBodySchemaIsNotJson"), + getLocalizedString("core.common.invalidReason.MethodNotAllowed"), + getLocalizedString("core.common.invalidReason.UrlPathNotExist"), + ]; + const errorMessage2 = [ + getLocalizedString("core.common.invalidReason.PostBodyContainsRequiredUnsupportedSchema"), + getLocalizedString("core.common.invalidReason.ParamsContainRequiredUnsupportedSchema"), + getLocalizedString("core.common.invalidReason.ParamsContainsNestedObject"), + getLocalizedString("core.common.invalidReason.RequestBodyContainsNestedObject"), + getLocalizedString("core.common.invalidReason.ExceededRequiredParamsLimit"), + getLocalizedString("core.common.invalidReason.NoParameter"), + getLocalizedString("core.common.invalidReason.NoAPIInfo"), + ]; + + expect(res[0].content).equals( + getLocalizedString( + "core.common.NoSupportedApiCopilot", + "GET /api: " + + errorMessage1.join(", ") + + "\n" + + "GET /api2: " + + errorMessage2.join(", ") + + "\n" + + "GET /api3: unknown" + ) ); - expect(res[12].content).equals("unknown"); + expect(res[1].content).equals(getLocalizedString("error.copilot.noExtraAPICanBeAdded")); }); }); @@ -1012,7 +1171,8 @@ describe("listPluginExistingOperations", () => { ...teamsManifest, plugins: [ { - pluginFile: "resources/plugin.json", + file: "resources/plugin.json", + id: "plugin1", }, ], }; diff --git a/packages/fx-core/tests/component/generator/generator.test.ts b/packages/fx-core/tests/component/generator/generator.test.ts index d336339dcb..520ca89aa5 100644 --- a/packages/fx-core/tests/component/generator/generator.test.ts +++ b/packages/fx-core/tests/component/generator/generator.test.ts @@ -849,12 +849,14 @@ describe("Generator happy path", async () => { llmService: "llm-service-azure-openai", azureOpenAIKey: "test-key", azureOpenAIEndpoint: "test-endpoint", + azureOpenAIDeploymentName: "test-deployment", }); assert.equal(vars.useOpenAI, ""); assert.equal(vars.useAzureOpenAI, "true"); assert.equal(vars.openAIKey, ""); assert.equal(vars.azureOpenAIKey, "test-key"); assert.equal(vars.azureOpenAIEndpoint, "test-endpoint"); + assert.equal(vars.azureOpenAIDeploymentName, "test-deployment"); }); it("template variables when contains auth", async () => { diff --git a/packages/fx-core/tests/core/FxCore.test.ts b/packages/fx-core/tests/core/FxCore.test.ts index 0d70bf20ac..81964c9312 100644 --- a/packages/fx-core/tests/core/FxCore.test.ts +++ b/packages/fx-core/tests/core/FxCore.test.ts @@ -77,6 +77,7 @@ import { InvalidProjectError, MissingEnvironmentVariablesError, MissingRequiredInputError, + UserCancelError, } from "../../src/error/common"; import { NoNeedUpgradeError } from "../../src/error/upgrade"; import { @@ -1500,6 +1501,7 @@ describe("getQuestions", async () => { "llm-service", "azure-openai-key", "azure-openai-endpoint", + "azure-openai-deployment-name", "openai-key", "office-addin-framework-type", "folder", @@ -1540,6 +1542,7 @@ describe("getQuestions", async () => { "llm-service", "azure-openai-key", "azure-openai-endpoint", + "azure-openai-deployment-name", "openai-key", "office-addin-framework-type", "folder", @@ -1580,6 +1583,7 @@ describe("getQuestions", async () => { "llm-service", "azure-openai-key", "azure-openai-endpoint", + "azure-openai-deployment-name", "openai-key", "office-addin-framework-type", "folder", @@ -1621,6 +1625,7 @@ describe("getQuestions", async () => { "llm-service", "azure-openai-key", "azure-openai-endpoint", + "azure-openai-deployment-name", "openai-key", "office-addin-framework-type", "folder", @@ -1695,6 +1700,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isOk()); }); @@ -1714,7 +1720,8 @@ describe("copilotPlugin", async () => { const manifest = new TeamsAppManifest(); manifest.plugins = [ { - pluginFile: "ai-plugin.json", + file: "ai-plugin.json", + id: "plugin1", }, ]; const listResult: ListAPIResult = { @@ -1748,6 +1755,7 @@ describe("copilotPlugin", async () => { sinon.stub(manifestUtils, "getPluginFilePath").resolves(ok("ai-plugin.json")); sinon.stub(validationUtils, "validateInputs").resolves(undefined); sinon.stub(CopilotPluginHelper, "listPluginExistingOperations").resolves([]); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); if (result.isErr()) { console.log(result.error); @@ -1769,7 +1777,8 @@ describe("copilotPlugin", async () => { const manifest = new TeamsAppManifest(); manifest.plugins = [ { - pluginFile: "ai-plugin.json", + file: "ai-plugin.json", + id: "plugin1", }, ]; const listResult: ListAPIResult = { @@ -1802,6 +1811,7 @@ describe("copilotPlugin", async () => { sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); sinon.stub(CopilotPluginHelper, "listPluginExistingOperations").resolves([]); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isErr()); if (result.isErr()) { @@ -1824,7 +1834,8 @@ describe("copilotPlugin", async () => { const manifest = new TeamsAppManifest(); manifest.plugins = [ { - pluginFile: "ai-plugin.json", + file: "ai-plugin.json", + id: "plugin1", }, ]; const listResult: ListAPIResult = { @@ -1860,6 +1871,7 @@ describe("copilotPlugin", async () => { .resolves(err(new SystemError("testError", "testError", "", ""))); sinon.stub(validationUtils, "validateInputs").resolves(undefined); sinon.stub(CopilotPluginHelper, "listPluginExistingOperations").resolves([]); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isErr()); @@ -1934,6 +1946,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isErr()); if (result.isErr()) { @@ -2007,6 +2020,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isErr()); if (result.isErr()) { @@ -2086,6 +2100,7 @@ describe("copilotPlugin", async () => { const yamlString = jsyaml.dump(teamsappObject); sinon.stub(fs, "pathExists").resolves(true); sinon.stub(fs, "readFile").resolves(yamlString as any); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isErr()); if (result.isErr()) { @@ -2174,6 +2189,7 @@ describe("copilotPlugin", async () => { const yamlString = jsyaml.dump(teamsappObject); sinon.stub(fs, "pathExists").resolves(true); sinon.stub(fs, "readFile").resolves(yamlString as any); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isErr()); if (result.isErr()) { @@ -2247,6 +2263,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const teamsappObject = { provision: [ { @@ -2344,6 +2361,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const teamsappObject = { provision: [ { @@ -2481,6 +2499,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const teamsappObject = { provision: [ { @@ -2589,6 +2608,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const teamsappObject = { provision: [ { @@ -2737,6 +2757,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const teamsappObject = { provision: [ { @@ -2879,6 +2900,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const teamsappObject = { provision: [ { @@ -3026,6 +3048,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const teamsappObject = { provision: [ { @@ -3161,6 +3184,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const teamsappObject = { provision: [ { @@ -3297,6 +3321,7 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "list").resolves(listResult); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isOk()); }); @@ -3313,6 +3338,7 @@ describe("copilotPlugin", async () => { const core = new FxCore(tools); sinon.stub(SpecParser.prototype, "generate").throws(new Error("fakeError")); sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isErr()); @@ -3340,12 +3366,72 @@ describe("copilotPlugin", async () => { sinon.stub(SpecParser.prototype, "generate").throws(new Error("fakeError")); sinon.stub(validationUtils, "validateInputs").resolves(undefined); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isErr()); }); it("add API - SpecParserError", async () => { + const appName = await mockV3Project(); + const manifest = new TeamsAppManifest(); + manifest.composeExtensions = [ + { + composeExtensionType: "apiBased", + apiSpecificationFile: "apiSpecificationFiles/openapi.json", + commands: [], + }, + ]; + const inputs: Inputs = { + platform: Platform.VSCode, + [QuestionNames.Folder]: os.tmpdir(), + [QuestionNames.ApiSpecLocation]: "test.json", + [QuestionNames.ApiOperation]: ["testOperation"], + [QuestionNames.ManifestPath]: "manifest.json", + projectPath: path.join(os.tmpdir(), appName), + }; + const core = new FxCore(tools); + sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); + sinon.stub(tools.ui, "showMessage").resolves(ok("Add")); + + const result = await core.copilotPluginAddAPI(inputs); + assert.isTrue(result.isErr()); + }); + + it("add API - ui error", async () => { + const appName = await mockV3Project(); + const manifest = new TeamsAppManifest(); + manifest.composeExtensions = [ + { + composeExtensionType: "apiBased", + apiSpecificationFile: "apiSpecificationFiles/openapi.json", + commands: [], + }, + ]; + const inputs: Inputs = { + platform: Platform.VSCode, + [QuestionNames.Folder]: os.tmpdir(), + [QuestionNames.ApiSpecLocation]: "test.json", + [QuestionNames.ApiOperation]: ["testOperation"], + [QuestionNames.ManifestPath]: "manifest.json", + projectPath: path.join(os.tmpdir(), appName), + }; + const core = new FxCore(tools); + sinon.stub(validationUtils, "validateInputs").resolves(undefined); + sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); + sinon + .stub(tools.ui, "showMessage") + .resolves(err(new UserError("testSource", "testError", "", ""))); + + const result = await core.copilotPluginAddAPI(inputs); + assert.isTrue(result.isErr()); + if (result.isErr()) { + assert.equal(result.error.name, "testError"); + } + }); + + it("add API - not 'add' when confirm", async () => { const appName = await mockV3Project(); const manifest = new TeamsAppManifest(); manifest.composeExtensions = [ @@ -3369,9 +3455,13 @@ describe("copilotPlugin", async () => { .throws(new SpecParserError("fakeMessage", ErrorType.SpecNotValid)); sinon.stub(validationUtils, "validateInputs").resolves(undefined); sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); + sinon.stub(tools.ui, "showMessage").resolves(ok("")); const result = await core.copilotPluginAddAPI(inputs); assert.isTrue(result.isErr()); + if (result.isErr()) { + assert.isTrue(result.error instanceof UserCancelError); + } }); describe("listPluginApiSpecs", async () => { @@ -3383,7 +3473,8 @@ describe("copilotPlugin", async () => { const manifest = new TeamsAppManifest(); manifest.plugins = [ { - pluginFile: "ai-plugin.json", + file: "ai-plugin.json", + id: "plugin1", }, ]; sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); @@ -3423,7 +3514,8 @@ describe("copilotPlugin", async () => { const manifest = new TeamsAppManifest(); manifest.plugins = [ { - pluginFile: "ai-plugin.json", + file: "ai-plugin.json", + id: "plugin1", }, ]; sinon.stub(manifestUtils, "_readAppManifest").resolves(ok(manifest)); diff --git a/packages/fx-core/tests/question/create.test.ts b/packages/fx-core/tests/question/create.test.ts index a2312231c3..ec841eed51 100644 --- a/packages/fx-core/tests/question/create.test.ts +++ b/packages/fx-core/tests/question/create.test.ts @@ -58,6 +58,7 @@ import { programmingLanguageQuestion, officeAddinFrameworkQuestion, getAddinFrameworkOptions, + projectTypeQuestion, } from "../../src/question/create"; import { QuestionNames } from "../../src/question/questionNames"; import { QuestionTreeVisitor, traverse } from "../../src/ui/visitor"; @@ -251,9 +252,6 @@ describe("scaffold question", () => { }); it("traverse in vscode me from new api (none auth)", async () => { - mockedEnvRestore = mockedEnv({ - [FeatureFlagName.ApiKey]: "true", - }); const inputs: Inputs = { platform: Platform.VSCode, }; @@ -293,7 +291,7 @@ describe("scaffold question", () => { } else if (question.name === QuestionNames.ApiMEAuth) { const select = question as SingleSelectQuestion; const options = await select.dynamicOptions?.(inputs); - assert.isTrue(options?.length === 2); + assert.isTrue(options?.length === 3); return ok({ type: "success", result: ApiMessageExtensionAuthOptions.none().id }); } else if (question.name === QuestionNames.ProgrammingLanguage) { return ok({ type: "success", result: "javascript" }); @@ -316,7 +314,7 @@ describe("scaffold question", () => { } else if (question.name === QuestionNames.ApiMEAuth) { const select = question as SingleSelectQuestion; const options = select.staticOptions; - assert.isTrue(options.length === 2); + assert.isTrue(options.length === 3); return ok({ type: "success", result: ApiMessageExtensionAuthOptions.none().id }); } return ok({ type: "success", result: undefined }); @@ -334,9 +332,6 @@ describe("scaffold question", () => { }); it("traverse in vscode me from new api (key auth)", async () => { - mockedEnvRestore = mockedEnv({ - [FeatureFlagName.ApiKey]: "true", - }); const inputs: Inputs = { platform: Platform.VSCode, }; @@ -376,7 +371,7 @@ describe("scaffold question", () => { } else if (question.name === QuestionNames.ApiMEAuth) { const select = question as SingleSelectQuestion; const options = await select.dynamicOptions?.(inputs); - assert.isTrue(options?.length === 2); + assert.isTrue(options?.length === 3); return ok({ type: "success", result: ApiMessageExtensionAuthOptions.apiKey().id }); } else if (question.name === QuestionNames.ProgrammingLanguage) { return ok({ type: "success", result: "javascript" }); @@ -384,23 +379,6 @@ describe("scaffold question", () => { return ok({ type: "success", result: "test001" }); } else if (question.name === QuestionNames.Folder) { return ok({ type: "success", result: "./" }); - } else if (question.name === QuestionNames.MeArchitectureType) { - const select = question as SingleSelectQuestion; - const options = await select.staticOptions; - // Assert - assert.equal(options.length, 2); - // Assert - assert.equal(options.length, 2); - assert.deepEqual(options, [ - MeArchitectureOptions.newApi(), - MeArchitectureOptions.apiSpec(), - ]); - return ok({ type: "success", result: MeArchitectureOptions.newApi().id }); - } else if (question.name === QuestionNames.ApiMEAuth) { - const select = question as SingleSelectQuestion; - const options = select.staticOptions; - assert.isTrue(options.length === 2); - return ok({ type: "success", result: ApiMessageExtensionAuthOptions.apiKey().id }); } return ok({ type: "success", result: undefined }); }; @@ -417,10 +395,6 @@ describe("scaffold question", () => { }); it("traverse in vscode me from new api (sso auth)", async () => { - mockedEnvRestore = mockedEnv({ - [FeatureFlagName.ApiKey]: "true", - [FeatureFlagName.ApiMeSSO]: "true", - }); const inputs: Inputs = { platform: Platform.VSCode, }; @@ -1195,7 +1169,7 @@ describe("scaffold question", () => { } else if (question.name === QuestionNames.CustomCopilotRag) { const select = question as SingleSelectQuestion; const options = await select.dynamicOptions!(inputs); - assert.isTrue(options.length === 2); + assert.isTrue(options.length === 4); return ok({ type: "success", result: CustomCopilotRagOptions.customize().id }); } else if (question.name === QuestionNames.ProgrammingLanguage) { const select = question as SingleSelectQuestion; @@ -1211,6 +1185,8 @@ describe("scaffold question", () => { return ok({ type: "success", result: "testKey" }); } else if (question.name === QuestionNames.AzureOpenAIEndpoint) { return ok({ type: "success", result: "testEndppint" }); + } else if (question.name === QuestionNames.AzureOpenAIDeploymentName) { + return ok({ type: "success", result: "testAzureOpenAIDeploymentName" }); } else if (question.name === QuestionNames.Folder) { return ok({ type: "success", result: "./" }); } else if (question.name === QuestionNames.AppName) { @@ -1227,12 +1203,13 @@ describe("scaffold question", () => { QuestionNames.LLMService, QuestionNames.AzureOpenAIKey, QuestionNames.AzureOpenAIEndpoint, + QuestionNames.AzureOpenAIDeploymentName, QuestionNames.Folder, QuestionNames.AppName, ]); }); - it("RAG - Azure AI Search - Azure OpenAI", async () => { + it("RAG - Customize - Azure OpenAI wit empty endpoint", async () => { const inputs: Inputs = { platform: Platform.VSCode, }; @@ -1257,9 +1234,71 @@ describe("scaffold question", () => { assert.isTrue(options.length === 3); return ok({ type: "success", result: CapabilityOptions.customCopilotRag().id }); } else if (question.name === QuestionNames.CustomCopilotRag) { + const select = question as SingleSelectQuestion; + const options = await select.dynamicOptions!(inputs); + assert.isTrue(options.length === 4); + return ok({ type: "success", result: CustomCopilotRagOptions.customize().id }); + } else if (question.name === QuestionNames.ProgrammingLanguage) { + const select = question as SingleSelectQuestion; + const options = await select.dynamicOptions!(inputs); + assert.isTrue(options.length === 3); + return ok({ type: "success", result: "python" }); + } else if (question.name === QuestionNames.LLMService) { const select = question as SingleSelectQuestion; const options = await select.dynamicOptions!(inputs); assert.isTrue(options.length === 2); + return ok({ type: "success", result: "llm-service-azure-openai" }); + } else if (question.name === QuestionNames.AzureOpenAIKey) { + return ok({ type: "success", result: "testKey" }); + } else if (question.name === QuestionNames.Folder) { + return ok({ type: "success", result: "./" }); + } else if (question.name === QuestionNames.AppName) { + return ok({ type: "success", result: "test001" }); + } + return ok({ type: "success", result: undefined }); + }; + await traverse(createProjectQuestionNode(), inputs, ui, undefined, visitor); + assert.deepEqual(questions, [ + QuestionNames.ProjectType, + QuestionNames.Capabilities, + QuestionNames.CustomCopilotRag, + QuestionNames.ProgrammingLanguage, + QuestionNames.LLMService, + QuestionNames.AzureOpenAIKey, + QuestionNames.AzureOpenAIEndpoint, + QuestionNames.Folder, + QuestionNames.AppName, + ]); + }); + + it("RAG - Azure AI Search - Azure OpenAI", async () => { + const inputs: Inputs = { + platform: Platform.VSCode, + }; + const questions: string[] = []; + const visitor: QuestionTreeVisitor = async ( + question: Question, + ui: UserInteraction, + inputs: Inputs, + step?: number, + totalSteps?: number + ) => { + questions.push(question.name); + await callFuncs(question, inputs); + if (question.name === QuestionNames.ProjectType) { + const select = question as SingleSelectQuestion; + const options = await select.dynamicOptions!(inputs); + assert.isTrue(options.length === 5); + return ok({ type: "success", result: ProjectTypeOptions.customCopilot().id }); + } else if (question.name === QuestionNames.Capabilities) { + const select = question as SingleSelectQuestion; + const options = await select.dynamicOptions!(inputs); + assert.isTrue(options.length === 3); + return ok({ type: "success", result: CapabilityOptions.customCopilotRag().id }); + } else if (question.name === QuestionNames.CustomCopilotRag) { + const select = question as SingleSelectQuestion; + const options = await select.dynamicOptions!(inputs); + assert.isTrue(options.length === 4); return ok({ type: "success", result: CustomCopilotRagOptions.customize().id }); } else if (question.name === QuestionNames.ProgrammingLanguage) { const select = question as SingleSelectQuestion; @@ -1358,14 +1397,15 @@ describe("scaffold question", () => { QuestionNames.ProjectType, QuestionNames.Capabilities, QuestionNames.CustomCopilotRag, - // QuestionNames.ApiSpecLocation, - // QuestionNames.ApiOperation, - // QuestionNames.ProgrammingLanguage, - // QuestionNames.LLMService, - // QuestionNames.AzureOpenAIKey, - // QuestionNames.AzureOpenAIEndpoint, - // QuestionNames.Folder, - // QuestionNames.AppName, + QuestionNames.ApiSpecLocation, + QuestionNames.ApiOperation, + QuestionNames.ProgrammingLanguage, + QuestionNames.LLMService, + QuestionNames.AzureOpenAIKey, + QuestionNames.AzureOpenAIEndpoint, + QuestionNames.AzureOpenAIDeploymentName, + QuestionNames.Folder, + QuestionNames.AppName, ]); }); @@ -1424,12 +1464,13 @@ describe("scaffold question", () => { QuestionNames.ProjectType, QuestionNames.Capabilities, QuestionNames.CustomCopilotRag, - // QuestionNames.ProgrammingLanguage, - // QuestionNames.LLMService, - // QuestionNames.AzureOpenAIKey, - // QuestionNames.AzureOpenAIEndpoint, - // QuestionNames.Folder, - // QuestionNames.AppName, + QuestionNames.ProgrammingLanguage, + QuestionNames.LLMService, + QuestionNames.AzureOpenAIKey, + QuestionNames.AzureOpenAIEndpoint, + QuestionNames.AzureOpenAIDeploymentName, + QuestionNames.Folder, + QuestionNames.AppName, ]); }); @@ -2916,13 +2957,14 @@ describe("scaffold question", () => { sandbox.stub(axios, "get").resolves({ status: 200, data: manifest }); sandbox.stub(SpecParser.prototype, "validate").resolves({ status: ValidationStatus.Error, - errors: [{ content: "error", type: ErrorType.NoSupportedApi }], + errors: [{ content: "error", type: ErrorType.NoSupportedApi, data: [] }], warnings: [], }); const res = await (question.additionalValidationOnAccept as any).validFunc("url", inputs); - assert.equal(res, getLocalizedString("core.common.NoSupportedApi")); + const noAPIMessage = getLocalizedString("core.common.invalidReason.NoAPIs"); + assert.equal(res, getLocalizedString("core.common.NoSupportedApi", noAPIMessage)); }); it("invalid openAI plugin manifest spec - multiple errors", async () => { @@ -2943,7 +2985,7 @@ describe("scaffold question", () => { sandbox.stub(SpecParser.prototype, "validate").resolves({ status: ValidationStatus.Error, errors: [ - { content: "error", type: ErrorType.NoSupportedApi }, + { content: "error", type: ErrorType.NoSupportedApi, data: [] }, { content: "error2", type: ErrorType.RelativeServerUrlNotSupported }, ], warnings: [], @@ -2977,7 +3019,7 @@ describe("scaffold question", () => { sandbox.stub(SpecParser.prototype, "validate").resolves({ status: ValidationStatus.Error, errors: [ - { content: "error", type: ErrorType.NoSupportedApi }, + { content: "error", type: ErrorType.NoSupportedApi, data: [] }, { content: "error2", type: ErrorType.RelativeServerUrlNotSupported }, ], warnings: [], @@ -2986,9 +3028,10 @@ describe("scaffold question", () => { const res = await (question.additionalValidationOnAccept as any).validFunc("url", inputs); assert.equal( res, - `${getLocalizedString("core.common.NoSupportedApi")}\n${getLocalizedString( - "core.common.RelativeServerUrlNotSupported" - )}` + `${getLocalizedString( + "core.common.NoSupportedApi", + getLocalizedString("core.common.invalidReason.NoAPIs") + )}\n${getLocalizedString("core.common.RelativeServerUrlNotSupported")}` ); }); @@ -3751,4 +3794,53 @@ describe("scaffold question", () => { } }); }); + + describe("projectTypeQuestion", () => { + let mockedEnvRestore: RestoreFn = () => {}; + afterEach(() => { + mockedEnvRestore(); + }); + it("trigger from agent", async () => { + const question = projectTypeQuestion(); + const inputs: Inputs = { platform: Platform.CLI, agent: "office" }; + assert.isDefined(question.dynamicOptions); + if (question.dynamicOptions) { + const options = (await question.dynamicOptions(inputs)) as OptionItem[]; + const officeAddinOption = options.find( + (o) => o.id === ProjectTypeOptions.officeXMLAddin().id + ); + assert.isDefined(officeAddinOption); + } + }); + it("enable isOfficeJSONAddinEnabled()", async () => { + mockedEnvRestore = mockedEnv({ + [FeatureFlagName.OfficeAddin]: "true", + }); + const question = projectTypeQuestion(); + const inputs: Inputs = { platform: Platform.CLI }; + assert.isDefined(question.dynamicOptions); + if (question.dynamicOptions) { + const options = (await question.dynamicOptions(inputs)) as OptionItem[]; + const officeAddinOption = options.find( + (o) => o.id === ProjectTypeOptions.officeAddin(inputs.platform).id + ); + assert.isDefined(officeAddinOption); + } + }); + it("disable isOfficeJSONAddinEnabled()", async () => { + mockedEnvRestore = mockedEnv({ + [FeatureFlagName.OfficeAddin]: "false", + }); + const question = projectTypeQuestion(); + const inputs: Inputs = { platform: Platform.CLI }; + assert.isDefined(question.dynamicOptions); + if (question.dynamicOptions) { + const options = (await question.dynamicOptions(inputs)) as OptionItem[]; + const officeAddinOption = options.find( + (o) => o.id === ProjectTypeOptions.outlookAddin(inputs.platform).id + ); + assert.isDefined(officeAddinOption); + } + }); + }); }); diff --git a/packages/manifest/src/index.ts b/packages/manifest/src/index.ts index 8a3cb9cc6c..0c0f8bcff7 100644 --- a/packages/manifest/src/index.ts +++ b/packages/manifest/src/index.ts @@ -157,8 +157,7 @@ export class ManifestUtil { if ((manifest as TeamsAppManifest).plugins) { const apiPlugins = (manifest as TeamsAppManifest).plugins; - if (apiPlugins && apiPlugins.length > 0 && apiPlugins[0].pluginFile) - properties.isPlugin = true; + if (apiPlugins && apiPlugins.length > 0 && apiPlugins[0].file) properties.isPlugin = true; } return properties; diff --git a/packages/manifest/src/manifest.ts b/packages/manifest/src/manifest.ts index c04ee1226c..6c26b4758b 100644 --- a/packages/manifest/src/manifest.ts +++ b/packages/manifest/src/manifest.ts @@ -365,7 +365,8 @@ export interface ITogetherModeScene { } export interface IPlugin { - pluginFile: string; + file: string; + id: string; } export type AppManifest = Record; diff --git a/packages/spec-parser/src/constants.ts b/packages/spec-parser/src/constants.ts index 8798d8e04d..9277f8c664 100644 --- a/packages/spec-parser/src/constants.ts +++ b/packages/spec-parser/src/constants.ts @@ -115,4 +115,5 @@ export class ConstantString { static readonly CommandTitleMaxLens = 32; static readonly ParameterTitleMaxLens = 32; static readonly SMERequiredParamsMaxNum = 5; + static readonly DefaultPluginId = "plugin_1"; } diff --git a/packages/spec-parser/src/index.ts b/packages/spec-parser/src/index.ts index 0a55933670..ffe5f073af 100644 --- a/packages/spec-parser/src/index.ts +++ b/packages/spec-parser/src/index.ts @@ -16,6 +16,7 @@ export { ParseOptions, AdaptiveCard, ProjectType, + InvalidAPIInfo, } from "./interfaces"; export { ConstantString } from "./constants"; diff --git a/packages/spec-parser/src/interfaces.ts b/packages/spec-parser/src/interfaces.ts index 3e9d5c88e6..441548c005 100644 --- a/packages/spec-parser/src/interfaces.ts +++ b/packages/spec-parser/src/interfaces.ts @@ -289,3 +289,8 @@ export interface AuthInfo { authScheme: OpenAPIV3.SecuritySchemeObject; name: string; } + +export interface InvalidAPIInfo { + api: string; + reason: ErrorType[]; +} diff --git a/packages/spec-parser/src/manifestUpdater.ts b/packages/spec-parser/src/manifestUpdater.ts index 738d3edc31..1d8492c88f 100644 --- a/packages/spec-parser/src/manifestUpdater.ts +++ b/packages/spec-parser/src/manifestUpdater.ts @@ -38,7 +38,8 @@ export class ManifestUpdater { const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath); manifest.plugins = [ { - pluginFile: apiPluginRelativePath, + file: apiPluginRelativePath, + id: ConstantString.DefaultPluginId, }, ]; @@ -47,9 +48,10 @@ export class ManifestUpdater { ManifestUpdater.updateManifestDescription(manifest, spec); const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath); - const apiPlugin = ManifestUpdater.generatePluginManifestSchema( + const apiPlugin = await ManifestUpdater.generatePluginManifestSchema( spec, specRelativePath, + apiPluginFilePath, appName, options ); @@ -91,12 +93,13 @@ export class ManifestUpdater { return parameter; } - static generatePluginManifestSchema( + static async generatePluginManifestSchema( spec: OpenAPIV3.Document, specRelativePath: string, + apiPluginFilePath: string, appName: string, options: ParseOptions - ): PluginManifestSchema { + ): Promise { const functions: FunctionObject[] = []; const functionNames: string[] = []; @@ -187,24 +190,55 @@ export class ManifestUpdater { } } - const apiPlugin: PluginManifestSchema = { - schema_version: "v2", - name_for_human: appName, - description_for_human: spec.info.description ?? "", - functions: functions, - runtimes: [ - { - type: "OpenApi", - auth: { - type: "none", // TODO, support auth in the future - }, - spec: { - url: specRelativePath, - }, - run_for_functions: functionNames, + let apiPlugin: PluginManifestSchema; + if (await fs.pathExists(apiPluginFilePath)) { + apiPlugin = await fs.readJSON(apiPluginFilePath); + } else { + apiPlugin = { + schema_version: "v2", + name_for_human: "", + description_for_human: "", + functions: [], + runtimes: [], + }; + } + + apiPlugin.functions = apiPlugin.functions || []; + + for (const func of functions) { + const index = apiPlugin.functions?.findIndex((f) => f.name === func.name); + if (index === -1) { + apiPlugin.functions.push(func); + } else { + apiPlugin.functions[index] = func; + } + } + + apiPlugin.runtimes = apiPlugin.runtimes || []; + const index = apiPlugin.runtimes.findIndex((runtime) => runtime.spec.url === specRelativePath); + if (index === -1) { + apiPlugin.runtimes.push({ + type: "OpenApi", + auth: { + type: "none", }, - ], - }; + spec: { + url: specRelativePath, + }, + run_for_functions: functionNames, + }); + } else { + apiPlugin.runtimes[index].run_for_functions = functionNames; + } + + if (!apiPlugin.name_for_human) { + apiPlugin.name_for_human = appName; + } + + if (!apiPlugin.description_for_human) { + apiPlugin.description_for_human = + spec.info.description ?? ""; + } return apiPlugin; } diff --git a/packages/spec-parser/src/validators/smeValidator.ts b/packages/spec-parser/src/validators/smeValidator.ts index df679007b8..57be0f53c7 100644 --- a/packages/spec-parser/src/validators/smeValidator.ts +++ b/packages/spec-parser/src/validators/smeValidator.ts @@ -40,8 +40,10 @@ export class SMEValidator extends Validator { result.errors.push(...validationResult.errors); // validate operationId missing - validationResult = this.validateSpecOperationId(); - result.warnings.push(...validationResult.warnings); + if (this.options.allowMissingId) { + validationResult = this.validateSpecOperationId(); + result.warnings.push(...validationResult.warnings); + } return result; } diff --git a/packages/spec-parser/src/validators/validator.ts b/packages/spec-parser/src/validators/validator.ts index 67df382180..956c261515 100644 --- a/packages/spec-parser/src/validators/validator.ts +++ b/packages/spec-parser/src/validators/validator.ts @@ -12,6 +12,7 @@ import { APIMap, SpecValidationResult, WarningType, + InvalidAPIInfo, } from "../interfaces"; import { Utils } from "../utils"; import { ConstantString } from "../constants"; @@ -80,9 +81,17 @@ export abstract class Validator { const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid); if (validAPIs.length === 0) { + const data = []; + for (const key in apiMap) { + const { reason } = apiMap[key]; + const apiInvalidReason: InvalidAPIInfo = { api: key, reason: reason }; + data.push(apiInvalidReason); + } + result.errors.push({ type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi, + data, }); } diff --git a/packages/spec-parser/test/browser/specParser.browser.test.ts b/packages/spec-parser/test/browser/specParser.browser.test.ts index 7efc82761e..7f0f5e96f1 100644 --- a/packages/spec-parser/test/browser/specParser.browser.test.ts +++ b/packages/spec-parser/test/browser/specParser.browser.test.ts @@ -470,7 +470,72 @@ describe("SpecParser in Browser", () => { warnings: [], errors: [ { type: ErrorType.NoServerInformation, content: ConstantString.NoServerInformation }, - { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi }, + { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi, data: [] }, + ], + }); + sinon.assert.calledOnce(dereferenceStub); + }); + + it("should return no supported API error with invalid api info", async function () { + const specPath = "path/to/spec"; + const spec = { + openapi: "3.0.2", + servers: [ + { + url: "https://servers1", + }, + ], + paths: { + "/pet": { + get: { + tags: ["pet"], + summary: "Get pet information from the store", + parameters: [ + { + name: "tags", + in: "query", + description: "Tags to filter by", + schema: { + type: "string", + }, + }, + ], + responses: { + "200": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Pet", + }, + }, + }, + }, + }, + }, + }, + }, + }; + + const specParser = new SpecParser(specPath, { allowMissingId: false }); + const parseStub = sinon.stub(specParser.parser, "parse").resolves(spec as any); + const dereferenceStub = sinon.stub(specParser.parser, "dereference").resolves(spec as any); + const validateStub = sinon.stub(specParser.parser, "validate").resolves(spec as any); + const result = await specParser.validate(); + + expect(result).to.deep.equal({ + status: ValidationStatus.Error, + warnings: [], + errors: [ + { + type: ErrorType.NoSupportedApi, + content: ConstantString.NoSupportedApi, + data: [ + { + api: "GET /pet", + reason: [ErrorType.MissingOperationId], + }, + ], + }, ], }); sinon.assert.calledOnce(dereferenceStub); @@ -495,7 +560,7 @@ describe("SpecParser in Browser", () => { content: Utils.format(ConstantString.UrlProtocolNotSupported, "http"), data: "http", }, - { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi }, + { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi, data: [] }, ], }); sinon.assert.calledOnce(dereferenceStub); @@ -524,7 +589,7 @@ describe("SpecParser in Browser", () => { }, ], }, - { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi }, + { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi, data: [] }, ], }); sinon.assert.calledOnce(dereferenceStub); @@ -543,7 +608,9 @@ describe("SpecParser in Browser", () => { expect(result).to.deep.equal({ status: ValidationStatus.Error, warnings: [], - errors: [{ type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi }], + errors: [ + { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi, data: [] }, + ], }); sinon.assert.calledOnce(dereferenceStub); }); diff --git a/packages/spec-parser/test/manifestUpdater.test.ts b/packages/spec-parser/test/manifestUpdater.test.ts index 60d35dbd1c..8a904a24fb 100644 --- a/packages/spec-parser/test/manifestUpdater.test.ts +++ b/packages/spec-parser/test/manifestUpdater.test.ts @@ -75,7 +75,6 @@ describe("updateManifestWithAiPlugin", () => { const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; const pluginFilePath = "/path/to/your/ai-plugin.json"; - sinon.stub(fs, "pathExists").resolves(true); const originalManifest = { name: { short: "Original Name", full: "Original Full Name" }, description: { short: "Original Short Description", full: "Original Full Description" }, @@ -85,7 +84,8 @@ describe("updateManifestWithAiPlugin", () => { description: { short: "My API", full: "My API description" }, plugins: [ { - pluginFile: "ai-plugin.json", + file: "ai-plugin.json", + id: "plugin_1", }, ], }; @@ -138,6 +138,588 @@ describe("updateManifestWithAiPlugin", () => { ], }; sinon.stub(fs, "readJSON").resolves(originalManifest); + sinon + .stub(fs, "pathExists") + .withArgs(manifestPath) + .resolves(true) + .withArgs(pluginFilePath) + .resolves(false); + + const options: ParseOptions = { + allowMethods: ["get", "post"], + }; + const [manifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin( + manifestPath, + outputSpecPath, + pluginFilePath, + spec, + options + ); + + expect(manifest).to.deep.equal(expectedManifest); + expect(apiPlugin).to.deep.equal(expectedPlugins); + }); + + it("should append new runtime to apiPlugin files if there exists different spec path", async () => { + const spec: any = { + openapi: "3.0.2", + info: { + title: "My API", + description: "My API description", + }, + servers: [ + { + url: "/v3", + }, + ], + paths: { + "/pets": { + get: { + operationId: "getPets", + summary: "Get all pets", + description: "Returns all pets from the system that the user has access to", + parameters: [ + { + name: "limit", + description: "Maximum number of pets to return", + required: true, + schema: { + type: "integer", + }, + }, + ], + }, + post: { + operationId: "createPet", + summary: "Create a pet", + description: "Create a new pet in the store", + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + required: ["name"], + properties: { + name: { + type: "string", + description: "Name of the pet", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + const manifestPath = "/path/to/your/manifest.json"; + const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; + const pluginFilePath = "/path/to/your/ai-plugin.json"; + + const originalManifest = { + name: { short: "Original Name", full: "Original Full Name" }, + description: { short: "Original Short Description", full: "Original Full Description" }, + }; + const expectedManifest = { + name: { short: "Original Name", full: "Original Full Name" }, + description: { short: "My API", full: "My API description" }, + plugins: [ + { + file: "ai-plugin.json", + id: "plugin_1", + }, + ], + }; + + const expectedPlugins: PluginManifestSchema = { + schema_version: "v2", + name_for_human: "Original Name", + description_for_human: "My API description", + functions: [ + { + name: "getPets2", + description: "Returns all pets from the system that the user has access to", + parameters: { + type: "object", + properties: { + limit: { + type: "integer", + description: "Maximum number of pets to return", + }, + }, + required: ["limit"], + }, + }, + { + name: "createPet2", + description: "Create a new pet in the store", + parameters: { + type: "object", + required: ["name"], + properties: { + name: { + type: "string", + description: "Name of the pet", + }, + }, + }, + }, + { + name: "getPets", + description: "Returns all pets from the system that the user has access to", + parameters: { + type: "object", + properties: { + limit: { + type: "integer", + description: "Maximum number of pets to return", + }, + }, + required: ["limit"], + }, + }, + { + name: "createPet", + description: "Create a new pet in the store", + parameters: { + type: "object", + required: ["name"], + properties: { + name: { + type: "string", + description: "Name of the pet", + }, + }, + }, + }, + ], + runtimes: [ + { + type: "OpenApi", + auth: { + type: "none", + }, + spec: { + url: "spec/outputSpec2.yaml", + }, + run_for_functions: ["getPets2", "createPet2"], + }, + { + type: "OpenApi", + auth: { + type: "none", + }, + spec: { + url: "spec/outputSpec.yaml", + }, + run_for_functions: ["getPets", "createPet"], + }, + ], + }; + sinon + .stub(fs, "pathExists") + .withArgs(manifestPath) + .resolves(true) + .withArgs(pluginFilePath) + .resolves(true); + sinon + .stub(fs, "readJSON") + .withArgs(manifestPath) + .resolves(originalManifest) + .withArgs(pluginFilePath) + .resolves({ + schema_version: "v2", + name_for_human: "", + description_for_human: "", + functions: [ + { + name: "getPets2", + description: "Returns all pets from the system that the user has access to", + parameters: { + type: "object", + properties: { + limit: { + type: "integer", + description: "Maximum number of pets to return", + }, + }, + required: ["limit"], + }, + }, + { + name: "createPet2", + description: "Create a new pet in the store", + parameters: { + type: "object", + required: ["name"], + properties: { + name: { + type: "string", + description: "Name of the pet", + }, + }, + }, + }, + ], + runtimes: [ + { + type: "OpenApi", + auth: { + type: "none", + }, + spec: { + url: "spec/outputSpec2.yaml", + }, + run_for_functions: ["getPets2", "createPet2"], + }, + ], + }); + + const options: ParseOptions = { + allowMethods: ["get", "post"], + }; + const [manifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin( + manifestPath, + outputSpecPath, + pluginFilePath, + spec, + options + ); + + expect(manifest).to.deep.equal(expectedManifest); + expect(apiPlugin).to.deep.equal(expectedPlugins); + }); + + it("should add runtime and functions if not exist", async () => { + const spec: any = { + openapi: "3.0.2", + info: { + title: "My API", + description: "My API description", + }, + servers: [ + { + url: "/v3", + }, + ], + paths: { + "/pets": { + get: { + operationId: "getPets", + summary: "Get all pets", + description: "Returns all pets from the system that the user has access to", + parameters: [ + { + name: "limit", + description: "Maximum number of pets to return", + required: true, + schema: { + type: "integer", + }, + }, + ], + }, + post: { + operationId: "createPet", + summary: "Create a pet", + description: "Create a new pet in the store", + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + required: ["name"], + properties: { + name: { + type: "string", + description: "Name of the pet", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + const manifestPath = "/path/to/your/manifest.json"; + const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; + const pluginFilePath = "/path/to/your/ai-plugin.json"; + + const originalManifest = { + name: { short: "Original Name", full: "Original Full Name" }, + description: { short: "Original Short Description", full: "Original Full Description" }, + }; + const expectedManifest = { + name: { short: "Original Name", full: "Original Full Name" }, + description: { short: "My API", full: "My API description" }, + plugins: [ + { + file: "ai-plugin.json", + id: "plugin_1", + }, + ], + }; + + const expectedPlugins: PluginManifestSchema = { + schema_version: "v2", + name_for_human: "exist_name", + description_for_human: "exist_description", + functions: [ + { + name: "getPets", + description: "Returns all pets from the system that the user has access to", + parameters: { + type: "object", + properties: { + limit: { + type: "integer", + description: "Maximum number of pets to return", + }, + }, + required: ["limit"], + }, + }, + { + name: "createPet", + description: "Create a new pet in the store", + parameters: { + type: "object", + required: ["name"], + properties: { + name: { + type: "string", + description: "Name of the pet", + }, + }, + }, + }, + ], + runtimes: [ + { + type: "OpenApi", + auth: { + type: "none", + }, + spec: { + url: "spec/outputSpec.yaml", + }, + run_for_functions: ["getPets", "createPet"], + }, + ], + }; + sinon + .stub(fs, "pathExists") + .withArgs(manifestPath) + .resolves(true) + .withArgs(pluginFilePath) + .resolves(true); + sinon + .stub(fs, "readJSON") + .withArgs(manifestPath) + .resolves(originalManifest) + .withArgs(pluginFilePath) + .resolves({ + schema_version: "v2", + name_for_human: "exist_name", + description_for_human: "exist_description", + }); + + const options: ParseOptions = { + allowMethods: ["get", "post"], + }; + const [manifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin( + manifestPath, + outputSpecPath, + pluginFilePath, + spec, + options + ); + + expect(manifest).to.deep.equal(expectedManifest); + expect(apiPlugin).to.deep.equal(expectedPlugins); + }); + + it("should overwrite apiPlugin files if there exists runtime with same spec path", async () => { + const spec: any = { + openapi: "3.0.2", + info: { + title: "My API", + description: "My API description", + }, + servers: [ + { + url: "/v3", + }, + ], + paths: { + "/pets": { + get: { + operationId: "getPets", + summary: "Get all pets", + description: "Returns all pets from the system that the user has access to", + parameters: [ + { + name: "limit", + description: "Maximum number of pets to return", + required: true, + schema: { + type: "integer", + }, + }, + ], + }, + post: { + operationId: "createPet", + summary: "Create a pet", + description: "Create a new pet in the store", + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + required: ["name"], + properties: { + name: { + type: "string", + description: "Name of the pet", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + const manifestPath = "/path/to/your/manifest.json"; + const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; + const pluginFilePath = "/path/to/your/ai-plugin.json"; + + const originalManifest = { + name: { short: "Original Name", full: "Original Full Name" }, + description: { short: "Original Short Description", full: "Original Full Description" }, + }; + const expectedManifest = { + name: { short: "Original Name", full: "Original Full Name" }, + description: { short: "My API", full: "My API description" }, + plugins: [ + { + file: "ai-plugin.json", + id: "plugin_1", + }, + ], + }; + + const expectedPlugins: PluginManifestSchema = { + schema_version: "v2", + name_for_human: "Original Name", + description_for_human: "My API description", + functions: [ + { + name: "getPets", + description: "Returns all pets from the system that the user has access to", + parameters: { + type: "object", + properties: { + limit: { + type: "integer", + description: "Maximum number of pets to return", + }, + }, + required: ["limit"], + }, + }, + { + name: "createPet", + description: "Create a new pet in the store", + parameters: { + type: "object", + required: ["name"], + properties: { + name: { + type: "string", + description: "Name of the pet", + }, + }, + }, + }, + ], + runtimes: [ + { + type: "OpenApi", + auth: { + type: "none", + }, + spec: { + url: "spec/outputSpec.yaml", + }, + run_for_functions: ["getPets", "createPet"], + }, + ], + }; + sinon + .stub(fs, "pathExists") + .withArgs(manifestPath) + .resolves(true) + .withArgs(pluginFilePath) + .resolves(true); + sinon + .stub(fs, "readJSON") + .withArgs(manifestPath) + .resolves(originalManifest) + .withArgs(pluginFilePath) + .resolves({ + schema_version: "v2", + name_for_human: "", + description_for_human: "", + functions: [ + { + name: "getPets", + description: "Returns all pets from the system that the user has access to - old", + parameters: { + type: "object", + properties: { + limit: { + type: "integer", + description: "Maximum number of pets to return - old", + }, + }, + required: ["limit"], + }, + }, + { + name: "createPet", + description: "Create a new pet in the store - old", + parameters: { + type: "object", + required: ["name"], + properties: { + name: { + type: "string", + description: "Name of the pet - old", + }, + }, + }, + }, + ], + runtimes: [ + { + type: "OpenApi", + auth: { + type: "none", + }, + spec: { + url: "spec/outputSpec.yaml", + }, + run_for_functions: ["getPets", "createPet"], + }, + ], + }); + const options: ParseOptions = { allowMethods: ["get", "post"], }; @@ -210,7 +792,12 @@ describe("updateManifestWithAiPlugin", () => { const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; const pluginFilePath = "/path/to/your/ai-plugin.json"; - sinon.stub(fs, "pathExists").resolves(true); + sinon + .stub(fs, "pathExists") + .withArgs(manifestPath) + .resolves(true) + .withArgs(pluginFilePath) + .resolves(false); const originalManifest = { name: { short: "Original Name${{TestEnv}}", full: "Original Full Name" }, description: { @@ -223,7 +810,8 @@ describe("updateManifestWithAiPlugin", () => { description: { short: "My API", full: "My API description" }, plugins: [ { - pluginFile: "ai-plugin.json", + file: "ai-plugin.json", + id: "plugin_1", }, ], }; @@ -354,7 +942,12 @@ describe("updateManifestWithAiPlugin", () => { const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; const pluginFilePath = "/path/to/your/ai-plugin.json"; - sinon.stub(fs, "pathExists").resolves(true); + sinon + .stub(fs, "pathExists") + .withArgs(manifestPath) + .resolves(true) + .withArgs(pluginFilePath) + .resolves(false); const originalManifest = { name: { short: "Original Name", full: "Original Full Name" }, description: { short: "Original Short Description", full: "Original Full Description" }, @@ -364,7 +957,8 @@ describe("updateManifestWithAiPlugin", () => { description: { short: "My API", full: "My API description" }, plugins: [ { - pluginFile: "ai-plugin.json", + file: "ai-plugin.json", + id: "plugin_1", }, ], }; @@ -455,7 +1049,12 @@ describe("updateManifestWithAiPlugin", () => { const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; const pluginFilePath = "/path/to/your/ai-plugin.json"; - sinon.stub(fs, "pathExists").resolves(true); + sinon + .stub(fs, "pathExists") + .withArgs(manifestPath) + .resolves(true) + .withArgs(pluginFilePath) + .resolves(false); const originalManifest = { name: { short: "Original Name", full: "Original Full Name" }, description: { short: "Original Short Description", full: "Original Full Description" }, @@ -465,7 +1064,8 @@ describe("updateManifestWithAiPlugin", () => { description: { short: "My API", full: "My API description" }, plugins: [ { - pluginFile: "ai-plugin.json", + file: "ai-plugin.json", + id: "plugin_1", }, ], }; @@ -546,16 +1146,20 @@ describe("updateManifestWithAiPlugin", () => { }; const manifestPath = "/path/to/your/manifest.json"; const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; - sinon.stub(fs, "pathExists").resolves(true); + const originalManifest = { name: { short: "Original Name", full: "Original Full Name" }, description: { short: "My API", full: "My API description" }, }; sinon.stub(fs, "readJSON").resolves(originalManifest); - const pluginFilePath = "/path/to/your/ai-plugin.json"; - + sinon + .stub(fs, "pathExists") + .withArgs(manifestPath) + .resolves(true) + .withArgs(pluginFilePath) + .resolves(false); try { const options: ParseOptions = { allowMethods: ["get", "post"], @@ -621,7 +1225,7 @@ describe("updateManifestWithAiPlugin", () => { }; const manifestPath = "/path/to/your/manifest.json"; const outputSpecPath = "/path/to/your/spec/outputSpec.yaml"; - sinon.stub(fs, "pathExists").resolves(true); + const originalManifest = { name: { short: "Original Name", full: "Original Full Name" }, description: { short: "My API", full: "My API description" }, @@ -629,7 +1233,12 @@ describe("updateManifestWithAiPlugin", () => { sinon.stub(fs, "readJSON").resolves(originalManifest); const pluginFilePath = "/path/to/your/ai-plugin.json"; - + sinon + .stub(fs, "pathExists") + .withArgs(manifestPath) + .resolves(true) + .withArgs(pluginFilePath) + .resolves(false); try { const options: ParseOptions = { allowMethods: ["get", "post"], diff --git a/packages/spec-parser/test/specParser.test.ts b/packages/spec-parser/test/specParser.test.ts index 4f6d53adac..6211574d1c 100644 --- a/packages/spec-parser/test/specParser.test.ts +++ b/packages/spec-parser/test/specParser.test.ts @@ -249,7 +249,7 @@ describe("SpecParser", () => { warnings: [], errors: [ { type: ErrorType.NoServerInformation, content: ConstantString.NoServerInformation }, - { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi }, + { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi, data: [] }, ], }); sinon.assert.calledOnce(dereferenceStub); @@ -274,7 +274,7 @@ describe("SpecParser", () => { content: Utils.format(ConstantString.UrlProtocolNotSupported, "http"), data: "http", }, - { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi }, + { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi, data: [] }, ], }); sinon.assert.calledOnce(dereferenceStub); @@ -303,7 +303,7 @@ describe("SpecParser", () => { }, ], }, - { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi }, + { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi, data: [] }, ], }); sinon.assert.calledOnce(dereferenceStub); @@ -322,7 +322,9 @@ describe("SpecParser", () => { expect(result).to.deep.equal({ status: ValidationStatus.Error, warnings: [], - errors: [{ type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi }], + errors: [ + { type: ErrorType.NoSupportedApi, content: ConstantString.NoSupportedApi, data: [] }, + ], }); sinon.assert.calledOnce(dereferenceStub); }); @@ -442,6 +444,71 @@ describe("SpecParser", () => { sinon.assert.calledOnce(dereferenceStub); }); + it("should return no supported API error with invalid api info", async function () { + const specPath = "path/to/spec"; + const spec = { + openapi: "3.0.2", + servers: [ + { + url: "https://servers1", + }, + ], + paths: { + "/pet": { + get: { + tags: ["pet"], + summary: "Get pet information from the store", + parameters: [ + { + name: "tags", + in: "query", + description: "Tags to filter by", + schema: { + type: "string", + }, + }, + ], + responses: { + "200": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/Pet", + }, + }, + }, + }, + }, + }, + }, + }, + }; + + const specParser = new SpecParser(specPath, { allowMissingId: false }); + const parseStub = sinon.stub(specParser.parser, "parse").resolves(spec as any); + const dereferenceStub = sinon.stub(specParser.parser, "dereference").resolves(spec as any); + const validateStub = sinon.stub(specParser.parser, "validate").resolves(spec as any); + const result = await specParser.validate(); + + expect(result).to.deep.equal({ + status: ValidationStatus.Error, + warnings: [], + errors: [ + { + type: ErrorType.NoSupportedApi, + content: ConstantString.NoSupportedApi, + data: [ + { + api: "GET /pet", + reason: [ErrorType.MissingOperationId], + }, + ], + }, + ], + }); + sinon.assert.calledOnce(dereferenceStub); + }); + it("should return a valid result when the spec is valid", async () => { const specPath = "path/to/spec"; const spec = { diff --git a/packages/tests/scripts/randomCases.json b/packages/tests/scripts/randomCases.json index 7abe554529..1ec04de72e 100644 --- a/packages/tests/scripts/randomCases.json +++ b/packages/tests/scripts/randomCases.json @@ -138,6 +138,16 @@ "node-16": [] } }, + "cases": [ + "sample-remotedebug-chef-bot" + ] + }, + { + "os": { + "windows-latest": { + "node-18": [] + } + }, "cases": [ "sample-remotedebug-todo-list-with-spfx", "sample-remotedebug-react-retail-dashboard", diff --git a/packages/tests/src/e2e/samples/ProvisionChefBot.tests.ts b/packages/tests/src/e2e/samples/ProvisionChefBot.tests.ts index efda386548..0d7a85d479 100644 --- a/packages/tests/src/e2e/samples/ProvisionChefBot.tests.ts +++ b/packages/tests/src/e2e/samples/ProvisionChefBot.tests.ts @@ -29,10 +29,10 @@ class ChefBotTestCase extends CaseFactory { public override async onAfterCreate(projectPath: string): Promise { expect(fs.pathExistsSync(path.resolve(projectPath, "infra"))).to.be.true; - const userFile = path.resolve(projectPath, ".env"); - const KEY = "OPENAI_KEY=MY_OPENAI_API_KEY"; + const userFile = path.resolve(projectPath, "env", ".env.dev.user"); + const KEY = "SECRET_OPENAI_KEY=MY_OPENAI_API_KEY"; fs.writeFileSync(userFile, KEY); - console.log(`add key ${KEY} to .env file`); + console.log(`add key ${KEY} to .env.dev.user file`); } } diff --git a/packages/tests/src/ui-test/migration/basic-tab/basic-tab-provision-upgrade-provision-debug.test.ts b/packages/tests/src/ui-test/migration/basic-tab/basic-tab-provision-upgrade-provision-debug.test.ts index 59c272f9f0..a7c39aa0b8 100644 --- a/packages/tests/src/ui-test/migration/basic-tab/basic-tab-provision-upgrade-provision-debug.test.ts +++ b/packages/tests/src/ui-test/migration/basic-tab/basic-tab-provision-upgrade-provision-debug.test.ts @@ -70,8 +70,14 @@ describe("Migration Tests", function () { CliHelper.setV3Enable(); // v3 provision - await reRunProvision(); - await reRunDeploy(Timeout.botDeploy); + await mirgationDebugTestContext.provisionProject( + mirgationDebugTestContext.appName, + mirgationDebugTestContext.projectPath + ); + await mirgationDebugTestContext.deployProject( + mirgationDebugTestContext.projectPath, + Timeout.botDeploy + ); // UI verify const teamsAppId = await mirgationDebugTestContext.getTeamsAppId("dev"); diff --git a/packages/tests/src/ui-test/migration/basic-tab/basic-tab-upgrade-provision-debug.test.ts b/packages/tests/src/ui-test/migration/basic-tab/basic-tab-upgrade-provision-debug.test.ts index 23a266a41b..ca66a8b2b1 100644 --- a/packages/tests/src/ui-test/migration/basic-tab/basic-tab-upgrade-provision-debug.test.ts +++ b/packages/tests/src/ui-test/migration/basic-tab/basic-tab-upgrade-provision-debug.test.ts @@ -64,8 +64,14 @@ describe("Migration Tests", function () { CliHelper.setV3Enable(); // v3 provision - await runProvision(mirgationDebugTestContext.appName); - await runDeploy(Timeout.botDeploy); + await mirgationDebugTestContext.provisionProject( + mirgationDebugTestContext.appName, + mirgationDebugTestContext.projectPath + ); + await mirgationDebugTestContext.deployProject( + mirgationDebugTestContext.projectPath, + Timeout.botDeploy + ); // UI verify const teamsAppId = await mirgationDebugTestContext.getTeamsAppId("dev"); diff --git a/packages/tests/src/ui-test/migration/bot/bot-provision-upgrade-provision-debug.test.ts b/packages/tests/src/ui-test/migration/bot/bot-provision-upgrade-provision-debug.test.ts index 510ce91317..ebe8f1e3c2 100644 --- a/packages/tests/src/ui-test/migration/bot/bot-provision-upgrade-provision-debug.test.ts +++ b/packages/tests/src/ui-test/migration/bot/bot-provision-upgrade-provision-debug.test.ts @@ -67,8 +67,14 @@ describe("Migration Tests", function () { CliHelper.setV3Enable(); // v3 provision - await reRunProvision(); - await reRunDeploy(Timeout.botDeploy); + await mirgationDebugTestContext.provisionProject( + mirgationDebugTestContext.appName, + mirgationDebugTestContext.projectPath + ); + await mirgationDebugTestContext.deployProject( + mirgationDebugTestContext.projectPath, + Timeout.botDeploy + ); // UI verify const teamsAppId = await mirgationDebugTestContext.getTeamsAppId("dev"); diff --git a/packages/tests/src/ui-test/migration/bot/bot-upgrade-provision-debug.test.ts b/packages/tests/src/ui-test/migration/bot/bot-upgrade-provision-debug.test.ts index ddad34bea4..23b483d6cd 100644 --- a/packages/tests/src/ui-test/migration/bot/bot-upgrade-provision-debug.test.ts +++ b/packages/tests/src/ui-test/migration/bot/bot-upgrade-provision-debug.test.ts @@ -59,8 +59,14 @@ describe("Migration Tests", function () { CliHelper.setV3Enable(); // v3 provision - await runProvision(mirgationDebugTestContext.appName); - await runDeploy(Timeout.botDeploy); + await mirgationDebugTestContext.provisionProject( + mirgationDebugTestContext.appName, + mirgationDebugTestContext.projectPath + ); + await mirgationDebugTestContext.deployProject( + mirgationDebugTestContext.projectPath, + Timeout.botDeploy + ); // UI verify const teamsAppId = await mirgationDebugTestContext.getTeamsAppId("dev"); diff --git a/packages/tests/src/ui-test/migration/migrationContext.ts b/packages/tests/src/ui-test/migration/migrationContext.ts index 7d1cbba999..45df0de2f6 100644 --- a/packages/tests/src/ui-test/migration/migrationContext.ts +++ b/packages/tests/src/ui-test/migration/migrationContext.ts @@ -8,6 +8,7 @@ import { Trigger, Framework, TestFilePath, + Timeout, } from "../../utils/constants"; import { TestContext } from "../testContext"; import { CliHelper } from "../cliHelper"; @@ -18,9 +19,11 @@ import { cleanAppStudio, cleanTeamsApp, GraphApiCleanHelper, + createResourceGroup, } from "../../utils/cleanHelper"; import { isV3Enabled } from "@microsoft/teamsfx-core"; import { AzSqlHelper } from "../../utils/azureCliHelper"; +import { runProvision, runDeploy } from "../remotedebug/remotedebugContext"; export class MigrationTestContext extends TestContext { public testName: Capability; @@ -252,4 +255,88 @@ export class MigrationTestContext extends TestContext { await cleanTeamsApp(this.appName); await cleanAppStudio(this.appName); } + + public async provisionProject( + appName: string, + projectPath = "", + createRg = true, + tool: "ttk" | "cli" = "cli", + option = "", + env: "dev" | "local" = "dev", + processEnv?: NodeJS.ProcessEnv + ) { + if (tool === "cli") { + await this.runCliProvision( + projectPath, + appName, + createRg, + option, + env, + processEnv + ); + } else { + await runProvision(appName); + } + } + + public async deployProject( + projectPath: string, + waitTime: number = Timeout.tabDeploy, + tool: "ttk" | "cli" = "cli", + option = "", + env: "dev" | "local" = "dev", + processEnv?: NodeJS.ProcessEnv, + retries?: number, + newCommand?: string + ) { + if (tool === "cli") { + await this.runCliDeploy( + projectPath, + option, + env, + processEnv, + retries, + newCommand + ); + } else { + await runDeploy(waitTime); + } + } + + public async runCliProvision( + projectPath: string, + appName: string, + createRg = true, + option = "", + env: "dev" | "local" = "dev", + processEnv?: NodeJS.ProcessEnv + ) { + if (createRg) { + await createResourceGroup(appName, env, "westus"); + } + const resourceGroupName = `${appName}-${env}-rg`; + await CliHelper.showVersion(projectPath, processEnv); + await CliHelper.provisionProject2(projectPath, option, env, { + ...process.env, + AZURE_RESOURCE_GROUP_NAME: resourceGroupName, + }); + } + + public async runCliDeploy( + projectPath: string, + option = "", + env: "dev" | "local" = "dev", + processEnv?: NodeJS.ProcessEnv, + retries?: number, + newCommand?: string + ) { + await CliHelper.deployAll( + projectPath, + option, + env, + processEnv, + retries, + newCommand + ); + } } diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-command-and-response-ts-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-command-and-response-ts-win-only.test.ts index e9c5c504b3..06d1d709aa 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-command-and-response-ts-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-command-and-response-ts-win-only.test.ts @@ -9,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -69,8 +69,8 @@ describe("Remote debug Tests", function () { const driver = VSBrowser.instance.driver; await createNewProject("crbot", appName, "TypeScript"); validateFileExist(projectPath, "src/index.ts"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-command-and-response-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-command-and-response-win-only.test.ts index 447de30851..658870bcc7 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-command-and-response-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-command-and-response-win-only.test.ts @@ -9,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -69,8 +69,8 @@ describe("Remote debug Tests", function () { const driver = VSBrowser.instance.driver; await createNewProject("crbot", appName); validateFileExist(projectPath, "src/index.js"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-dashboard-ts-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-dashboard-ts-win-only.test.ts index 7ad53cebf6..f51ebecd44 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-dashboard-ts-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-dashboard-ts-win-only.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Ivan Chen */ @@ -5,8 +8,8 @@ import * as path from "path"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -63,8 +66,8 @@ describe("Remote debug Tests", function () { }, async function () { await createNewProject("dashboard", appName, "TypeScript"); - await runProvision(appName); - await runDeploy(); + await provisionProject(appName, projectPath); + await deployProject(projectPath); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-dashboard-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-dashboard-win-only.test.ts index 28852bdcbf..74a9bf585d 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-dashboard-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-dashboard-win-only.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Ivan Chen */ @@ -5,8 +8,8 @@ import * as path from "path"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -63,8 +66,8 @@ describe("Remote debug Tests", function () { }, async function () { await createNewProject("dashboard", appName, "JavaScript"); - await runProvision(appName); - await runDeploy(); + await provisionProject(appName, projectPath); + await deployProject(projectPath); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-link-unfurling-ts-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-link-unfurling-ts-win-only.test.ts index 1e8d6b9f03..6fb951b52c 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-link-unfurling-ts-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-link-unfurling-ts-win-only.test.ts @@ -9,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -66,8 +66,8 @@ describe("Remote debug Tests", function () { async function () { const driver = VSBrowser.instance.driver; await createNewProject("linkunfurl", appName, "TypeScript"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-link-unfurling-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-link-unfurling-win-only.test.ts index dbfbde177a..0a0e1cb532 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-link-unfurling-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-link-unfurling-win-only.test.ts @@ -9,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -66,8 +66,8 @@ describe("Remote debug Tests", function () { async function () { const driver = VSBrowser.instance.driver; await createNewProject("linkunfurl", appName); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-msg-newapi-ts-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-msg-newapi-ts-win-only.test.ts index 333d565459..5abaeda0c9 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-msg-newapi-ts-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-msg-newapi-ts-win-only.test.ts @@ -9,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -69,8 +69,8 @@ describe("Remote debug Tests", function () { async function () { const driver = VSBrowser.instance.driver; await createNewProject("msgnewapi", appName, "TypeScript"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-msg-newapi-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-msg-newapi-win-only.test.ts index e332b1a365..d266feaaa7 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-msg-newapi-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-msg-newapi-win-only.test.ts @@ -9,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -69,8 +69,8 @@ describe("Remote debug Tests", function () { async function () { const driver = VSBrowser.instance.driver; await createNewProject("msgnewapi", appName); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-msg-ts-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-msg-ts-win-only.test.ts index b0838a8954..e8427c2b7f 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-msg-ts-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-msg-ts-win-only.test.ts @@ -9,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -65,8 +65,8 @@ describe("Remote debug Tests", function () { async function () { const driver = VSBrowser.instance.driver; await createNewProject("msg", appName, "TypeScript"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-msg-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-msg-win-only.test.ts index 7c38088cb5..023280f857 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-msg-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-msg-win-only.test.ts @@ -9,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -66,8 +66,8 @@ describe("Remote debug Tests", function () { async function () { const driver = VSBrowser.instance.driver; await createNewProject("msg", appName); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-msgsa-ts-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-msgsa-ts-win-only.test.ts index fb82ef5336..04a52848df 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-msgsa-ts-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-msgsa-ts-win-only.test.ts @@ -9,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -65,8 +65,8 @@ describe("Remote debug Tests", function () { async function () { const driver = VSBrowser.instance.driver; await createNewProject("msgsa", appName, "TypeScript"); - await runProvision(appName); - await runDeploy(); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-msgsa-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-msgsa-win-only.test.ts index a7bfffcec6..7a0f8b90c7 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-msgsa-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-msgsa-win-only.test.ts @@ -10,8 +10,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -66,8 +66,8 @@ describe("Remote debug Tests", function () { async function () { const driver = VSBrowser.instance.driver; await createNewProject("msgsa", appName); - await runProvision(appName); - await runDeploy(); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-timertrigger-ts-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-timertrigger-ts-win-only.test.ts index 9bcc1b9ff6..816125559f 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-timertrigger-ts-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-timertrigger-ts-win-only.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Helly Zhang */ @@ -6,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -73,8 +76,8 @@ describe("Remote debug Tests", function () { await createNewProject("functimernoti", appName, "TypeScript"); validateFileExist(projectPath, "src/httpTrigger.ts"); validateFileExist(projectPath, "src/timerTrigger.ts"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-timertrigger-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-timertrigger-win-only.test.ts index e30cc6126c..d709592a1b 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-timertrigger-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-timertrigger-win-only.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Helly Zhang */ @@ -6,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -72,8 +75,8 @@ describe("Remote debug Tests", function () { await createNewProject("functimernoti", appName); validateFileExist(projectPath, "src/httpTrigger.js"); validateFileExist(projectPath, "src/timerTrigger.js"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-ts-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-ts-win-only.test.ts index 7c21faa1fe..d84305633c 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-ts-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-ts-win-only.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Helly Zhang */ @@ -6,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -70,8 +73,8 @@ describe("Remote debug Tests", function () { const driver = VSBrowser.instance.driver; await createNewProject("funcnoti", appName, "TypeScript"); validateFileExist(projectPath, "src/httpTrigger.ts"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-win-only.test.ts index 4aa179da9b..16a24b2260 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-func-win-only.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Helly Zhang */ @@ -6,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -70,8 +73,8 @@ describe("Remote debug Tests", function () { const driver = VSBrowser.instance.driver; await createNewProject("funcnoti", appName); validateFileExist(projectPath, "src/httpTrigger.js"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-restify-ts-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-restify-ts-win-only.test.ts index 478f7c0936..4ee77edbcf 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-restify-ts-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-restify-ts-win-only.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Helly Zhang */ @@ -6,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -15,7 +18,6 @@ import { } from "../../utils/vscodeOperation"; import { initPage, - validateBot, validateNotificationBot, } from "../../utils/playwrightOperation"; import { Env } from "../../utils/env"; @@ -71,8 +73,8 @@ describe("Remote debug Tests", function () { const driver = VSBrowser.instance.driver; await createNewProject("restnoti", appName, "TypeScript"); validateFileExist(projectPath, "src/index.ts"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-restify-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-restify-win-only.test.ts index a19842ecf3..abe02f9d6f 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-restify-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-restify-win-only.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Helly Zhang */ @@ -6,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -15,7 +18,6 @@ import { } from "../../utils/vscodeOperation"; import { initPage, - validateBot, validateNotificationBot, } from "../../utils/playwrightOperation"; import { Env } from "../../utils/env"; @@ -71,8 +73,8 @@ describe("Remote debug Tests", function () { const driver = VSBrowser.instance.driver; await createNewProject("restnoti", appName); validateFileExist(projectPath, "src/index.js"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-timertrigger-ts-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-timertrigger-ts-win-only.test.ts index 0ca6ddab8c..ad68810bf5 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-timertrigger-ts-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-timertrigger-ts-win-only.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Anne Fu */ @@ -6,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -69,8 +72,8 @@ describe("Remote debug Tests", function () { const driver = VSBrowser.instance.driver; await createNewProject("timenoti", appName, "TypeScript"); validateFileExist(projectPath, "src/timerTrigger.ts"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-timertrigger-win-only.test.ts b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-timertrigger-win-only.test.ts index d9d0fb1f27..3f1f481f11 100644 --- a/packages/tests/src/ui-test/remotedebug/remotedebug-notification-timertrigger-win-only.test.ts +++ b/packages/tests/src/ui-test/remotedebug/remotedebug-notification-timertrigger-win-only.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + /** * @author Anne Fu */ @@ -6,8 +9,8 @@ import { VSBrowser } from "vscode-extension-tester"; import { Timeout } from "../../utils/constants"; import { RemoteDebugTestContext, - runProvision, - runDeploy, + provisionProject, + deployProject, } from "./remotedebugContext"; import { execCommandIfExist, @@ -15,7 +18,6 @@ import { } from "../../utils/vscodeOperation"; import { initPage, - validateNotificationBot, validateNotificationTimeBot, } from "../../utils/playwrightOperation"; import { Env } from "../../utils/env"; @@ -71,8 +73,8 @@ describe("Remote debug Tests", function () { const driver = VSBrowser.instance.driver; await createNewProject("timenoti", appName); validateFileExist(projectPath, "src/timerTrigger.js"); - await runProvision(appName); - await runDeploy(Timeout.botDeploy); + await provisionProject(appName, projectPath); + await deployProject(projectPath, Timeout.botDeploy); const teamsAppId = await remoteDebugTestContext.getTeamsAppId( projectPath ); diff --git a/packages/tests/src/ui-test/samples/sample-localdebug-chef-bot.test.ts b/packages/tests/src/ui-test/samples/sample-localdebug-chef-bot.test.ts index 1eeaab934c..5ea63515a5 100644 --- a/packages/tests/src/ui-test/samples/sample-localdebug-chef-bot.test.ts +++ b/packages/tests/src/ui-test/samples/sample-localdebug-chef-bot.test.ts @@ -23,11 +23,15 @@ class ChefBotTestCase extends CaseFactory { sampledebugContext: SampledebugContext, env: "local" | "dev" ): Promise { - const envFile = path.resolve(sampledebugContext.projectPath, ".env"); - // create .env file - fs.writeFileSync(envFile, "OPENAI_KEY=yourapikey"); - console.log(`add OPENAI_KEY=yourapikey to .env file`); - await sampledebugContext.prepareDebug("yarn"); + const envFile = path.resolve( + sampledebugContext.projectPath, + "env", + ".env.local.user" + ); + // create .env.local.user file + fs.writeFileSync(envFile, "SECRET_OPENAI_KEY=yourapikey"); + console.log(`add SECRET_OPENAI_KEY=yourapikey to .env file`); + // await sampledebugContext.prepareDebug("yarn"); } override async onValidate(page: Page): Promise { console.log("Moked api key. Only verify happy path..."); diff --git a/packages/tests/src/ui-test/samples/sample-remotedebug-chef-bot.test.ts b/packages/tests/src/ui-test/samples/sample-remotedebug-chef-bot.test.ts index cbfcc46d12..5514962eb3 100644 --- a/packages/tests/src/ui-test/samples/sample-remotedebug-chef-bot.test.ts +++ b/packages/tests/src/ui-test/samples/sample-remotedebug-chef-bot.test.ts @@ -19,11 +19,15 @@ class ChefBotTestCase extends CaseFactory { sampledebugContext: SampledebugContext, env: "local" | "dev" ): Promise { - const envFile = path.resolve(sampledebugContext.projectPath, ".env"); - // create .env file - fs.writeFileSync(envFile, "OPENAI_KEY=yourapikey"); - console.log(`add OPENAI_KEY=yourapikey to .env file`); - await sampledebugContext.prepareDebug("yarn"); + const envFile = path.resolve( + sampledebugContext.projectPath, + "env", + ".env.dev.user" + ); + // create .env.local.user file + fs.writeFileSync(envFile, "SECRET_OPENAI_KEY=yourapikey"); + console.log(`add SECRET_OPENAI_KEY=yourapikey to .env file`); + // await sampledebugContext.prepareDebug("yarn"); } override async onValidate(page: Page): Promise { console.log("Moked api key. Only verify happy path..."); diff --git a/packages/tests/src/ui-test/samples/sampleCaseFactory.ts b/packages/tests/src/ui-test/samples/sampleCaseFactory.ts index 78319f8009..da261b7980 100644 --- a/packages/tests/src/ui-test/samples/sampleCaseFactory.ts +++ b/packages/tests/src/ui-test/samples/sampleCaseFactory.ts @@ -24,11 +24,6 @@ import { SampledebugContext } from "./sampledebugContext"; import { it } from "../../utils/it"; import { VSBrowser } from "vscode-extension-tester"; import { getScreenshotName } from "../../utils/nameUtil"; -import { - runProvision, - runDeploy, - reRunDeploy, -} from "../remotedebug/remotedebugContext"; import { AzSqlHelper } from "../../utils/azureCliHelper"; import { expect } from "chai"; import { Page } from "playwright"; @@ -364,17 +359,14 @@ export abstract class CaseFactory { } }, dev: async () => { - await runProvision( + await sampledebugContext.provisionProject( sampledebugContext.appName, - env, - false, - options?.type === "spfx" + sampledebugContext.projectPath + ); + await sampledebugContext.deployProject( + sampledebugContext.projectPath, + Timeout.botDeploy ); - try { - await runDeploy(Timeout.tabDeploy); - } catch (error) { - await reRunDeploy(Timeout.tabDeploy); - } }, }; diff --git a/packages/tests/src/ui-test/samples/sampledebugContext.ts b/packages/tests/src/ui-test/samples/sampledebugContext.ts index 1176720f18..331666990d 100644 --- a/packages/tests/src/ui-test/samples/sampledebugContext.ts +++ b/packages/tests/src/ui-test/samples/sampledebugContext.ts @@ -30,8 +30,10 @@ import { cleanAppStudio, cleanUpLocalProject, cleanUpResourceGroup, + createResourceGroup, } from "../../utils/cleanHelper"; import { Executor } from "../../utils/executor"; +import { runProvision, runDeploy } from "../remotedebug/remotedebugContext"; export class SampledebugContext extends TestContext { public readonly appName: string; @@ -415,4 +417,88 @@ export class SampledebugContext extends TestContext { console.log("stderr: ", stderr); } } + + public async provisionProject( + appName: string, + projectPath = "", + createRg = true, + tool: "ttk" | "cli" = "cli", + option = "", + env: "dev" | "local" = "dev", + processEnv?: NodeJS.ProcessEnv + ) { + if (tool === "cli") { + await this.runCliProvision( + projectPath, + appName, + createRg, + option, + env, + processEnv + ); + } else { + await runProvision(appName); + } + } + + public async deployProject( + projectPath: string, + waitTime: number = Timeout.tabDeploy, + tool: "ttk" | "cli" = "cli", + option = "", + env: "dev" | "local" = "dev", + processEnv?: NodeJS.ProcessEnv, + retries?: number, + newCommand?: string + ) { + if (tool === "cli") { + await this.runCliDeploy( + projectPath, + option, + env, + processEnv, + retries, + newCommand + ); + } else { + await runDeploy(waitTime); + } + } + + public async runCliProvision( + projectPath: string, + appName: string, + createRg = true, + option = "", + env: "dev" | "local" = "dev", + processEnv?: NodeJS.ProcessEnv + ) { + if (createRg) { + await createResourceGroup(appName, env, "westus"); + } + const resourceGroupName = `${appName}-${env}-rg`; + await CliHelper.showVersion(projectPath, processEnv); + await CliHelper.provisionProject2(projectPath, option, env, { + ...process.env, + AZURE_RESOURCE_GROUP_NAME: resourceGroupName, + }); + } + + public async runCliDeploy( + projectPath: string, + option = "", + env: "dev" | "local" = "dev", + processEnv?: NodeJS.ProcessEnv, + retries?: number, + newCommand?: string + ) { + await CliHelper.deployAll( + projectPath, + option, + env, + processEnv, + retries, + newCommand + ); + } } diff --git a/packages/tests/src/ui-test/treeview/treeview-invalidname.test.ts b/packages/tests/src/ui-test/treeview/treeview-invalidname.test.ts index cfd63dd748..5271293220 100644 --- a/packages/tests/src/ui-test/treeview/treeview-invalidname.test.ts +++ b/packages/tests/src/ui-test/treeview/treeview-invalidname.test.ts @@ -22,7 +22,7 @@ describe("New project Tests", function () { let testRootFolder: string; let nodeVersion: string | null; const warnMsg = - "Application name must start with letters and contain at least two letters or digits. It can not contain some special characters."; + "App name needs to begin with letters, include minimum two letters or digits, and exclude certain special characters."; beforeEach(async function () { // ensure workbench is ready diff --git a/packages/tests/src/utils/azureCliHelper.ts b/packages/tests/src/utils/azureCliHelper.ts index fe995f2698..50453c93be 100644 --- a/packages/tests/src/utils/azureCliHelper.ts +++ b/packages/tests/src/utils/azureCliHelper.ts @@ -107,7 +107,7 @@ export class AzSqlHelper { } static async login() { - const command = `az login -u ${Env["azureAccountName"]} -p '${Env["azureAccountPassword"]}'`; + const command = `az login -u ${Env["azureAccountName"]} -p ${Env["azureAccountPassword"]}`; await Executor.execute(command, process.cwd()); // set subscription const subscription = Env["azureSubscriptionId"]; @@ -237,7 +237,7 @@ export class AzServiceBusHelper { } static async login() { - const command = `az login -u ${Env["azureAccountName"]} -p '${Env["azureAccountPassword"]}'`; + const command = `az login -u ${Env["azureAccountName"]} -p ${Env["azureAccountPassword"]}`; await Executor.execute(command, process.cwd()); // set subscription diff --git a/packages/vscode-extension/package.nls.json b/packages/vscode-extension/package.nls.json index 708717b329..f90336d9f2 100644 --- a/packages/vscode-extension/package.nls.json +++ b/packages/vscode-extension/package.nls.json @@ -173,6 +173,8 @@ "teamstoolkit.commandsTreeViewProvider.validateManifestDescription": "Validate the manifest file of Office add-ins project", "teamstoolkit.commandsTreeViewProvider.scriptLabTitle": "Script Lab", "teamstoolkit.commandsTreeViewProvider.scriptLabDescription": "Open Script Lab introduction page", + "teamstoolkit.commandsTreeViewProvider.promptLibraryTitle": "View Prompts for GitHub Copilot", + "teamstoolkit.commandsTreeViewProvider.promptLibraryDescription": "Open Office Prompt Library for GitHub Copilot", "teamstoolkit.commandsTreeViewProvider.officeAddIn.officePartnerCenterTitle": "Open Partner Center", "teamstoolkit.commandsTreeViewProvider.officeAddIn.officePartnerCenterDescription": "Open Partner Center", "teamstoolkit.commandsTreeViewProvider.officeAddIn.getStartedTitle": "Get Started", @@ -223,8 +225,10 @@ "teamstoolkit.handlers.autoInstallDependency": "Dependency installation in progress...", "teamstoolkit.handlers.adaptiveCardExtUsage": "Type \"Adaptive Card: Open Preview\" in command pallete to start previewing current Adaptive Card file.", "teamstoolkit.handlers.invalidProject": "Unable to debug Teams App. This is not a valid Teams project.", - "teamstoolkit.handlers.localDebugDescription": "[%s] is successfully created at [local address](%s). Continue to debug your app in Test Tool or Teams.", - "teamstoolkit.handlers.localDebugDescription.fallback": "[%s] is successfully created at %s. Continue to debug your app in Test Tool or Teams.", + "teamstoolkit.handlers.localDebugDescription": "[%s] is successfully created at [local address](%s). Continue to debug your app in Teams.", + "teamstoolkit.handlers.localDebugDescription.fallback": "[%s] is successfully created at %s. Continue to debug your app in Teams.", + "teamstoolkit.handlers.localDebugDescription.enabledTestTool": "[%s] is successfully created at [local address](%s). Continue to debug your app in Test Tool or Teams.", + "teamstoolkit.handlers.localDebugDescription.enabledTestTool.fallback": "[%s] is successfully created at %s. Continue to debug your app in Test Tool or Teams.", "teamstoolkit.handlers.localDebugTitle": "Debug", "teamstoolkit.handlers.localPreviewDescription": "[%s] is successfully created at [local address](%s). Continue to preview your app.", "teamstoolkit.handlers.localPreviewDescription.fallback": "[%s] is successfully created at %s. Continue to preview your app.", diff --git a/packages/vscode-extension/src/extension.ts b/packages/vscode-extension/src/extension.ts index 2cecdc2e46..01cb600441 100644 --- a/packages/vscode-extension/src/extension.ts +++ b/packages/vscode-extension/src/extension.ts @@ -754,6 +754,12 @@ function registerOfficeDevMenuCommands(context: vscode.ExtensionContext) { ); context.subscriptions.push(openScriptLabLinkCmd); + const openPromptLibraryLinkCmd = vscode.commands.registerCommand( + "fx-extension.openPromptLibraryLink", + (...args) => Correlator.run(officeDevHandlers.openPromptLibraryLink, args) + ); + context.subscriptions.push(openPromptLibraryLinkCmd); + // help and feedback const openHelpFeedbackLinkCmd = vscode.commands.registerCommand( "fx-extension.openOfficeDevHelpFeedbackLink", diff --git a/packages/vscode-extension/src/handlers.ts b/packages/vscode-extension/src/handlers.ts index 0c7ecdc64b..f92edd4a97 100644 --- a/packages/vscode-extension/src/handlers.ts +++ b/packages/vscode-extension/src/handlers.ts @@ -125,6 +125,7 @@ import TreeViewManagerInstance from "./treeview/treeViewManager"; import { anonymizeFilePaths, getAppName, + getLocalDebugMessageTemplate, getResourceGroupNameFromEnv, getSubscriptionInfoFromEnv, getTeamsAppTelemetryInfoByEnv, @@ -1388,19 +1389,13 @@ export async function showLocalDebugMessage() { ExtTelemetry.sendTelemetryEvent(TelemetryEvent.ShowLocalDebugNotification); const appName = (await getAppName()) ?? localize("teamstoolkit.handlers.fallbackAppName"); const isWindows = process.platform === "win32"; - let message = util.format( - localize("teamstoolkit.handlers.localDebugDescription.fallback"), - appName, - globalVariables.workspaceUri?.fsPath - ); + const messageTemplate = await getLocalDebugMessageTemplate(isWindows); + + let message = util.format(messageTemplate, appName, globalVariables.workspaceUri?.fsPath); if (isWindows) { const folderLink = encodeURI(globalVariables.workspaceUri!.toString()); const openFolderCommand = `command:fx-extension.openFolder?%5B%22${folderLink}%22%5D`; - message = util.format( - localize("teamstoolkit.handlers.localDebugDescription"), - appName, - openFolderCommand - ); + message = util.format(messageTemplate, appName, openFolderCommand); } void vscode.window.showInformationMessage(message, localDebug).then((selection) => { if (selection?.title === localize("teamstoolkit.handlers.localDebugTitle")) { diff --git a/packages/vscode-extension/src/officeDevHandlers.ts b/packages/vscode-extension/src/officeDevHandlers.ts index 3bcfbf4b4c..d8c525e471 100644 --- a/packages/vscode-extension/src/officeDevHandlers.ts +++ b/packages/vscode-extension/src/officeDevHandlers.ts @@ -123,11 +123,23 @@ export async function openReportIssues(args?: any[]): Promise> { + ExtTelemetry.sendTelemetryEvent(TelemetryEvent.Documentation, { + ...getTriggerFromProperty(args), + [TelemetryProperty.DocumentationName]: "office_scriptLab", + }); return VS_CODE_UI.openUrl( "https://learn.microsoft.com/office/dev/add-ins/overview/explore-with-script-lab" ); } +export async function openPromptLibraryLink(args?: any[]): Promise> { + ExtTelemetry.sendTelemetryEvent(TelemetryEvent.Documentation, { + ...getTriggerFromProperty(args), + [TelemetryProperty.DocumentationName]: "office_promptLibrary", + }); + return VS_CODE_UI.openUrl("https://aka.ms/OfficeAddinsPromptLibrary"); +} + export function validateOfficeAddInManifest(args?: any[]): Promise> { ExtTelemetry.sendTelemetryEvent( TelemetryEvent.validateAddInManifest, diff --git a/packages/vscode-extension/src/treeview/officeDevTreeViewManager.ts b/packages/vscode-extension/src/treeview/officeDevTreeViewManager.ts index 3d9b0b87e9..bfebe79b86 100644 --- a/packages/vscode-extension/src/treeview/officeDevTreeViewManager.ts +++ b/packages/vscode-extension/src/treeview/officeDevTreeViewManager.ts @@ -153,6 +153,16 @@ class OfficeDevTreeViewManager { custom: false, } ), + new TreeViewCommand( + localize("teamstoolkit.commandsTreeViewProvider.promptLibraryTitle"), + localize("teamstoolkit.commandsTreeViewProvider.promptLibraryDescription"), + "fx-extension.openPromptLibraryLink", + undefined, + { + name: "repo", + custom: false, + } + ), ]; return officeUtilityCommands; diff --git a/packages/vscode-extension/src/utils/commonUtils.ts b/packages/vscode-extension/src/utils/commonUtils.ts index 345df2ad5f..5d5ae07608 100644 --- a/packages/vscode-extension/src/utils/commonUtils.ts +++ b/packages/vscode-extension/src/utils/commonUtils.ts @@ -16,6 +16,8 @@ import { getV3TeamsAppId } from "../debug/commonUtils"; import * as globalVariables from "../globalVariables"; import { core } from "../handlers"; import { TelemetryProperty, TelemetryTriggerFrom } from "../telemetry/extTelemetryEvents"; +import { localize } from "./localizeUtils"; +import { workspace } from "vscode"; export function getPackageVersion(versionStr: string): string { if (versionStr.includes("alpha")) { @@ -371,3 +373,30 @@ function isAdaptiveCard(content: string): boolean { const pattern = /"type"\s*:\s*"AdaptiveCard"/; return pattern.test(content); } + +export async function getLocalDebugMessageTemplate(isWindows: boolean): Promise { + const enabledTestTool = await isTestToolEnabled(); + + if (isWindows) { + return enabledTestTool + ? localize("teamstoolkit.handlers.localDebugDescription.enabledTestTool") + : localize("teamstoolkit.handlers.localDebugDescription"); + } + + return enabledTestTool + ? localize("teamstoolkit.handlers.localDebugDescription.enabledTestTool.fallback") + : localize("teamstoolkit.handlers.localDebugDescription.fallback"); +} + +// check if test tool is enabled in scaffolded project +async function isTestToolEnabled(): Promise { + if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) { + const workspaceFolder = workspace.workspaceFolders[0]; + const workspacePath: string = workspaceFolder.uri.fsPath; + + const testToolYamlPath = path.join(workspacePath, "teamsapp.testtool.yml"); + return fs.pathExists(testToolYamlPath); + } + + return false; +} diff --git a/packages/vscode-extension/test/extension/codeLensProvider.test.ts b/packages/vscode-extension/test/extension/codeLensProvider.test.ts index 452a86ba1f..587de6e241 100644 --- a/packages/vscode-extension/test/extension/codeLensProvider.test.ts +++ b/packages/vscode-extension/test/extension/codeLensProvider.test.ts @@ -321,7 +321,8 @@ describe("Api plugin CodeLensProvider", () => { const manifest = new TeamsAppManifest(); manifest.plugins = [ { - pluginFile: "test.json", + file: "test.json", + id: "plugin1", }, ]; const openApiObject = { diff --git a/packages/vscode-extension/test/extension/commonUtils.test.ts b/packages/vscode-extension/test/extension/commonUtils.test.ts index 20e88de682..16f1cdae1f 100644 --- a/packages/vscode-extension/test/extension/commonUtils.test.ts +++ b/packages/vscode-extension/test/extension/commonUtils.test.ts @@ -1,4 +1,5 @@ import * as chai from "chai"; +import * as fs from "fs-extra"; import * as os from "os"; import * as sinon from "sinon"; import * as cp from "child_process"; @@ -467,4 +468,51 @@ describe("CommonUtils", () => { ); }); }); + + describe("getLocalDebugMessageTemplate()", () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { + sandbox.restore(); + }); + + it("Test Tool enabled in Windows platform", async () => { + sandbox.stub(vscode.workspace, "workspaceFolders").value([{ uri: vscode.Uri.file("test") }]); + sandbox.stub(fs, "pathExists").resolves(true); + + const result = await commonUtils.getLocalDebugMessageTemplate(true); + chai.assert.isTrue(result.includes("Test Tool")); + }); + + it("Test Tool disabled in Windows platform", async () => { + sandbox.stub(vscode.workspace, "workspaceFolders").value([{ uri: vscode.Uri.file("test") }]); + sandbox.stub(fs, "pathExists").resolves(false); + + const result = await commonUtils.getLocalDebugMessageTemplate(true); + chai.assert.isFalse(result.includes("Test Tool")); + }); + + it("Test Tool enabled in non-Windows platform", async () => { + sandbox.stub(vscode.workspace, "workspaceFolders").value([{ uri: vscode.Uri.file("test") }]); + sandbox.stub(fs, "pathExists").resolves(true); + + const result = await commonUtils.getLocalDebugMessageTemplate(false); + chai.assert.isTrue(result.includes("Test Tool")); + }); + + it("Test Tool disabled in non-Windows platform", async () => { + sandbox.stub(vscode.workspace, "workspaceFolders").value([{ uri: vscode.Uri.file("test") }]); + sandbox.stub(fs, "pathExists").resolves(false); + + const result = await commonUtils.getLocalDebugMessageTemplate(false); + chai.assert.isFalse(result.includes("Test Tool")); + }); + + it("No workspace folder", async () => { + sandbox.stub(vscode.workspace, "workspaceFolders").value([]); + sandbox.stub(fs, "pathExists").resolves(false); + + const result = await commonUtils.getLocalDebugMessageTemplate(false); + chai.assert.isFalse(result.includes("Test Tool")); + }); + }); }); diff --git a/packages/vscode-extension/test/extension/handlers.test.ts b/packages/vscode-extension/test/extension/handlers.test.ts index 14ddcb7a5d..3c7669d31d 100644 --- a/packages/vscode-extension/test/extension/handlers.test.ts +++ b/packages/vscode-extension/test/extension/handlers.test.ts @@ -2690,6 +2690,7 @@ describe("autoOpenProjectHandler", () => { it("showLocalDebugMessage()", async () => { sandbox.stub(vscode.workspace, "workspaceFolders").value([{ uri: vscode.Uri.file("test") }]); sandbox.stub(vscode.workspace, "openTextDocument"); + sandbox.stub(process, "platform").value("win32"); const executeCommandStub = sandbox.stub(vscode.commands, "executeCommand"); sandbox.stub(globalState, "globalStateGet").callsFake(async (key: string) => { diff --git a/packages/vscode-extension/test/extension/officeDevHandler.test.ts b/packages/vscode-extension/test/extension/officeDevHandler.test.ts index 76345bcb90..59d2477c01 100644 --- a/packages/vscode-extension/test/extension/officeDevHandler.test.ts +++ b/packages/vscode-extension/test/extension/officeDevHandler.test.ts @@ -113,6 +113,13 @@ describe("officeDevHandler", () => { ); }); + it("openPromptLibraryLink", async () => { + testOpenUrlHandler( + officeDevHandlers.openPromptLibraryLink, + "https://aka.ms/OfficeAddinsPromptLibrary" + ); + }); + it("popupOfficeAddInDependenciesMessage", async () => { const autoInstallDependencyHandlerStub = sandbox.stub(handlers, "autoInstallDependencyHandler"); sandbox.stub(localizeUtils, "localize").returns("installPopUp"); diff --git a/packages/vscode-extension/test/extension/treeview/officeDevTreeViewManager.test.ts b/packages/vscode-extension/test/extension/treeview/officeDevTreeViewManager.test.ts index 8e372e1751..fa92a2ec71 100644 --- a/packages/vscode-extension/test/extension/treeview/officeDevTreeViewManager.test.ts +++ b/packages/vscode-extension/test/extension/treeview/officeDevTreeViewManager.test.ts @@ -27,7 +27,7 @@ describe("OfficeDevTreeViewManager", () => { const utilityTreeView = officeDevTreeViewManager.getTreeView("teamsfx-officedev-utility"); chai.assert.isDefined(utilityTreeView); - chai.assert.equal((utilityTreeView as any).commands.length, 2); + chai.assert.equal((utilityTreeView as any).commands.length, 3); const helpAndFeedbackTreeView = officeDevTreeViewManager.getTreeView( "teamsfx-officedev-help-and-feedback" diff --git a/templates/common/api-plugin-existing-api/README.md b/templates/common/api-plugin-existing-api/README.md index a5d20aa5d8..e1dd99ceaf 100644 --- a/templates/common/api-plugin-existing-api/README.md +++ b/templates/common/api-plugin-existing-api/README.md @@ -16,6 +16,7 @@ This app template allows Teams to interact directly with third-party data, apps, > - [Node.js](https://nodejs.org/), supported versions: 16, 18 > - A [Microsoft 365 account for development](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts). > - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +> - [Copilot for Microsoft 365 license](https://learn.microsoft.com/microsoft-365-copilot/extensibility/prerequisites#prerequisites) 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. diff --git a/templates/common/api-plugin-existing-api/teamsapp.yml.tpl b/templates/common/api-plugin-existing-api/teamsapp.yml.tpl index 244da0baad..e716bf2fce 100644 --- a/templates/common/api-plugin-existing-api/teamsapp.yml.tpl +++ b/templates/common/api-plugin-existing-api/teamsapp.yml.tpl @@ -1,7 +1,7 @@ -# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.0.0/yaml.schema.json +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.1.0/yaml.schema.json # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file # Visit https://aka.ms/teamsfx-actions for details on actions -version: 1.0.0 +version: 1.1.0 environmentFolderPath: ./env @@ -17,11 +17,6 @@ provision: writeToEnvironmentFile: teamsAppId: TEAMS_APP_ID - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: @@ -29,11 +24,6 @@ provision: manifestPath: ./appPackage/manifest.json outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip # Apply the Teams app manifest to an existing Teams app in # Teams Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. @@ -54,11 +44,6 @@ provision: # Triggered when 'teamsapp publish' is executed publish: - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: @@ -66,11 +51,6 @@ publish: manifestPath: ./appPackage/manifest.json outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip # Apply the Teams app manifest to an existing Teams app in # Teams Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. diff --git a/templates/constraints/yml/templates/common/api-plugin-existing-api/teamsapp.yml.tpl.mustache b/templates/constraints/yml/templates/common/api-plugin-existing-api/teamsapp.yml.tpl.mustache new file mode 100644 index 0000000000..0cea5ce306 --- /dev/null +++ b/templates/constraints/yml/templates/common/api-plugin-existing-api/teamsapp.yml.tpl.mustache @@ -0,0 +1,17 @@ +{{#header}} version: 1.1.0 {{/header}} + +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: +{{#teamsAppCreate}} {{/teamsAppCreate}} + +{{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} +{{#teamsAppUpdate}} {{/teamsAppUpdate}} +{{#teamsAppExtendToM365}} {{/teamsAppExtendToM365}} + +# Triggered when 'teamsapp publish' is executed +publish: +{{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} +{{#teamsAppUpdate}} {{/teamsAppUpdate}} +{{#teamsAppPublishAppPackage}} {{/teamsAppPublishAppPackage}} diff --git a/templates/constraints/yml/templates/csharp/api-plugin-existing-api/teamsapp.yml.tpl.mustache b/templates/constraints/yml/templates/csharp/api-plugin-existing-api/teamsapp.yml.tpl.mustache new file mode 100644 index 0000000000..21acc838c5 --- /dev/null +++ b/templates/constraints/yml/templates/csharp/api-plugin-existing-api/teamsapp.yml.tpl.mustache @@ -0,0 +1,13 @@ +{{#header}} version: 1.1.0 {{/header}} + +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: +{{#teamsAppCreate}} {{/teamsAppCreate}} + +{{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} + +{{#teamsAppUpdate}} {{/teamsAppUpdate}} + +{{#teamsAppExtendToM365}} {{/teamsAppExtendToM365}} diff --git a/templates/constraints/yml/templates/csharp/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache b/templates/constraints/yml/templates/csharp/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache index c2ea875d5b..70b79fcd49 100644 --- a/templates/constraints/yml/templates/csharp/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache +++ b/templates/constraints/yml/templates/csharp/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache @@ -5,11 +5,8 @@ provision: {{#script}} COPILOT {{/script}} -{{#teamsAppValidateManifest}} {{/teamsAppValidateManifest}} - {{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} -{{#teamsAppValidateAppPackage}} {{/teamsAppValidateAppPackage}} {{#teamsAppUpdate}} {{/teamsAppUpdate}} diff --git a/templates/constraints/yml/templates/csharp/api-plugin-from-scratch/teamsapp.yml.tpl.mustache b/templates/constraints/yml/templates/csharp/api-plugin-from-scratch/teamsapp.yml.tpl.mustache index 8ad65c9e77..cddf3e2744 100644 --- a/templates/constraints/yml/templates/csharp/api-plugin-from-scratch/teamsapp.yml.tpl.mustache +++ b/templates/constraints/yml/templates/csharp/api-plugin-from-scratch/teamsapp.yml.tpl.mustache @@ -8,12 +8,8 @@ provision: {{#armDeploy}} deploymentName: Create-resources-for-sme {{/armDeploy}} -{{#teamsAppValidateManifest}} {{/teamsAppValidateManifest}} - {{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} -{{#teamsAppValidateAppPackage}} {{/teamsAppValidateAppPackage}} - {{#teamsAppUpdate}} {{/teamsAppUpdate}} {{#teamsAppExtendToM365}} {{/teamsAppExtendToM365}} diff --git a/templates/constraints/yml/templates/js/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache b/templates/constraints/yml/templates/js/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache index 9d4e42cefd..de4f973142 100644 --- a/templates/constraints/yml/templates/js/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache +++ b/templates/constraints/yml/templates/js/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache @@ -5,12 +5,8 @@ provision: {{#script}} FUNC, FUNC_NAME: repair {{/script}} -{{#teamsAppValidateManifest}} {{/teamsAppValidateManifest}} - {{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} -{{#teamsAppValidateAppPackage}} {{/teamsAppValidateAppPackage}} - {{#teamsAppUpdate}} {{/teamsAppUpdate}} {{#teamsAppExtendToM365}} {{/teamsAppExtendToM365}} diff --git a/templates/constraints/yml/templates/js/api-plugin-from-scratch/teamsapp.yml.tpl.mustache b/templates/constraints/yml/templates/js/api-plugin-from-scratch/teamsapp.yml.tpl.mustache index 9fcbacb22a..8c1cb587b5 100644 --- a/templates/constraints/yml/templates/js/api-plugin-from-scratch/teamsapp.yml.tpl.mustache +++ b/templates/constraints/yml/templates/js/api-plugin-from-scratch/teamsapp.yml.tpl.mustache @@ -8,12 +8,8 @@ provision: {{#armDeploy}} deploymentName: Create-resources-for-sme {{/armDeploy}} -{{#teamsAppValidateManifest}} {{/teamsAppValidateManifest}} - {{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} -{{#teamsAppValidateAppPackage}} {{/teamsAppValidateAppPackage}} - {{#teamsAppUpdate}} {{/teamsAppUpdate}} {{#teamsAppExtendToM365}} {{/teamsAppExtendToM365}} @@ -26,8 +22,6 @@ deploy: # Triggered when 'teamsapp publish' is executed publish: -{{#teamsAppValidateManifest}} {{/teamsAppValidateManifest}} {{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} -{{#teamsAppValidateAppPackage}} {{/teamsAppValidateAppPackage}} {{#teamsAppUpdate}} {{/teamsAppUpdate}} {{#teamsAppPublishAppPackage}} {{/teamsAppPublishAppPackage}} diff --git a/templates/constraints/yml/templates/ts/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache b/templates/constraints/yml/templates/ts/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache index 9d4e42cefd..de4f973142 100644 --- a/templates/constraints/yml/templates/ts/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache +++ b/templates/constraints/yml/templates/ts/api-plugin-from-scratch/teamsapp.local.yml.tpl.mustache @@ -5,12 +5,8 @@ provision: {{#script}} FUNC, FUNC_NAME: repair {{/script}} -{{#teamsAppValidateManifest}} {{/teamsAppValidateManifest}} - {{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} -{{#teamsAppValidateAppPackage}} {{/teamsAppValidateAppPackage}} - {{#teamsAppUpdate}} {{/teamsAppUpdate}} {{#teamsAppExtendToM365}} {{/teamsAppExtendToM365}} diff --git a/templates/constraints/yml/templates/ts/api-plugin-from-scratch/teamsapp.yml.tpl.mustache b/templates/constraints/yml/templates/ts/api-plugin-from-scratch/teamsapp.yml.tpl.mustache index ca2cd4680c..5bcb32e105 100644 --- a/templates/constraints/yml/templates/ts/api-plugin-from-scratch/teamsapp.yml.tpl.mustache +++ b/templates/constraints/yml/templates/ts/api-plugin-from-scratch/teamsapp.yml.tpl.mustache @@ -8,12 +8,8 @@ provision: {{#armDeploy}} deploymentName: Create-resources-for-sme {{/armDeploy}} -{{#teamsAppValidateManifest}} {{/teamsAppValidateManifest}} - {{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} -{{#teamsAppValidateAppPackage}} {{/teamsAppValidateAppPackage}} - {{#teamsAppUpdate}} {{/teamsAppUpdate}} {{#teamsAppExtendToM365}} {{/teamsAppExtendToM365}} @@ -28,8 +24,6 @@ deploy: # Triggered when 'teamsapp publish' is executed publish: -{{#teamsAppValidateManifest}} {{/teamsAppValidateManifest}} {{#teamsAppZipAppPackage}} {{/teamsAppZipAppPackage}} -{{#teamsAppValidateAppPackage}} {{/teamsAppValidateAppPackage}} {{#teamsAppUpdate}} {{/teamsAppUpdate}} {{#teamsAppPublishAppPackage}} {{/teamsAppPublishAppPackage}} diff --git a/templates/csharp/api-plugin-existing-api/GettingStarted.md b/templates/csharp/api-plugin-existing-api/GettingStarted.md index e6fa13904d..9d71c0b4aa 100644 --- a/templates/csharp/api-plugin-existing-api/GettingStarted.md +++ b/templates/csharp/api-plugin-existing-api/GettingStarted.md @@ -8,6 +8,12 @@ > > - [Visual Studio 2022](https://aka.ms/vs) 17.9 or higher and [install Teams Toolkit](https://aka.ms/install-teams-toolkit-vs) +1. Right-click your project and select `Teams Toolkit > Provision in the Cloud..`. You can find everything it will do in the `teamsapp.yml`. +2. If prompted, sign in with a Microsoft 365 account for the Teams organization you want +to install the app to. +3. Right-click your project and select `Teams Toolkit > Preview in > Teams`. +4. When Teams launches in the browser, open the `Copilot` app and send a prompt to trigger your plugin. + ## Learn more - [Extend Teams platform with APIs](https://aka.ms/teamsfx-api-plugin) diff --git a/templates/csharp/api-plugin-existing-api/teamsapp.yml.tpl b/templates/csharp/api-plugin-existing-api/teamsapp.yml.tpl index 09cff43629..b36552a281 100644 --- a/templates/csharp/api-plugin-existing-api/teamsapp.yml.tpl +++ b/templates/csharp/api-plugin-existing-api/teamsapp.yml.tpl @@ -17,12 +17,6 @@ provision: writeToEnvironmentFile: teamsAppId: TEAMS_APP_ID - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json - # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: @@ -31,12 +25,6 @@ provision: outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - # Apply the Teams app manifest to an existing Teams app in # Teams Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. diff --git a/templates/csharp/api-plugin-from-scratch/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl b/templates/csharp/api-plugin-from-scratch/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl index a31df153ea..e980a7e537 100644 --- a/templates/csharp/api-plugin-from-scratch/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl +++ b/templates/csharp/api-plugin-from-scratch/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl @@ -2,5 +2,6 @@ + diff --git a/templates/csharp/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl b/templates/csharp/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl index fb11edba12..c406a2a01e 100644 --- a/templates/csharp/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl +++ b/templates/csharp/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl @@ -17,20 +17,7 @@ }, "required": [ "assignedTo" - ] - }, - "states": { - "reasoning": { - "description": "Returns the repair records.", - "instructions": [ - "Here are the parameters:", - " assignedTo: The person assigned to the repair." - ] - }, - "responding": { - "description": "Returns the repair result in JSON format.", - "instructions": "Extract and include as much relevant information as possible from the JSON result to meet the user's needs." - } + ] } } ], diff --git a/templates/csharp/api-plugin-from-scratch/appPackage/manifest.json.tpl b/templates/csharp/api-plugin-from-scratch/appPackage/manifest.json.tpl index 9ebc549abb..e345ae0488 100644 --- a/templates/csharp/api-plugin-from-scratch/appPackage/manifest.json.tpl +++ b/templates/csharp/api-plugin-from-scratch/appPackage/manifest.json.tpl @@ -25,7 +25,8 @@ "accentColor": "#FFFFFF", "plugins": [ { - "pluginFile": "ai-plugin.json" + "file": "ai-plugin.json", + "id": "plugin_1" } ], "permissions": [ diff --git a/templates/csharp/api-plugin-from-scratch/{{ProjectName}}.csproj.tpl b/templates/csharp/api-plugin-from-scratch/{{ProjectName}}.csproj.tpl index 05e4306ca3..47acca0f98 100644 --- a/templates/csharp/api-plugin-from-scratch/{{ProjectName}}.csproj.tpl +++ b/templates/csharp/api-plugin-from-scratch/{{ProjectName}}.csproj.tpl @@ -8,10 +8,12 @@ {{SafeProjectName}} +{{^isNewProjectTypeEnabled}} - + +{{/isNewProjectTypeEnabled}} diff --git a/templates/csharp/copilot-plugin-from-scratch-api-key/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl b/templates/csharp/copilot-plugin-from-scratch-api-key/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl index a31df153ea..f5fd3a6c3a 100644 --- a/templates/csharp/copilot-plugin-from-scratch-api-key/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl +++ b/templates/csharp/copilot-plugin-from-scratch-api-key/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl @@ -2,5 +2,6 @@ + diff --git a/templates/csharp/copilot-plugin-from-scratch-api-key/{{ProjectName}}.csproj.tpl b/templates/csharp/copilot-plugin-from-scratch-api-key/{{ProjectName}}.csproj.tpl index 3faf0e8aa4..7adf03b09c 100644 --- a/templates/csharp/copilot-plugin-from-scratch-api-key/{{ProjectName}}.csproj.tpl +++ b/templates/csharp/copilot-plugin-from-scratch-api-key/{{ProjectName}}.csproj.tpl @@ -8,12 +8,12 @@ {{SafeProjectName}} - {{^isNewProjectTypeEnabled}} + -{{/isNewProjectTypeEnabled}} +{{/isNewProjectTypeEnabled}} {{^isNewProjectTypeEnabled}} diff --git a/templates/csharp/copilot-plugin-from-scratch/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl b/templates/csharp/copilot-plugin-from-scratch/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl index a31df153ea..f5fd3a6c3a 100644 --- a/templates/csharp/copilot-plugin-from-scratch/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl +++ b/templates/csharp/copilot-plugin-from-scratch/.{{NewProjectTypeName}}/{{NewProjectTypeName}}.{{NewProjectTypeExt}}.tpl @@ -2,5 +2,6 @@ + diff --git a/templates/csharp/copilot-plugin-from-scratch/{{ProjectName}}.csproj.tpl b/templates/csharp/copilot-plugin-from-scratch/{{ProjectName}}.csproj.tpl index 27f0dceb7f..69be9fde1a 100644 --- a/templates/csharp/copilot-plugin-from-scratch/{{ProjectName}}.csproj.tpl +++ b/templates/csharp/copilot-plugin-from-scratch/{{ProjectName}}.csproj.tpl @@ -8,12 +8,12 @@ {{SafeProjectName}} - {{^isNewProjectTypeEnabled}} + -{{/isNewProjectTypeEnabled}} +{{/isNewProjectTypeEnabled}} {{^isNewProjectTypeEnabled}} diff --git a/templates/js/ai-assistant-bot/.webappignore b/templates/js/ai-assistant-bot/.webappignore index 3e9dfd4d90..1759231814 100644 --- a/templates/js/ai-assistant-bot/.webappignore +++ b/templates/js/ai-assistant-bot/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/ai-assistant-bot/teamsapp.testtool.yml b/templates/js/ai-assistant-bot/teamsapp.testtool.yml index e53a7bc322..51495063dc 100644 --- a/templates/js/ai-assistant-bot/teamsapp.testtool.yml +++ b/templates/js/ai-assistant-bot/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/ai-bot/.gitignore b/templates/js/ai-bot/.gitignore index 1cbb2aad8d..07a76d44b3 100644 --- a/templates/js/ai-bot/.gitignore +++ b/templates/js/ai-bot/.gitignore @@ -14,3 +14,6 @@ node_modules/ .env .deployment .DS_Store + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/js/ai-bot/.webappignore b/templates/js/ai-bot/.webappignore index 18a015a2a3..f79d01ac12 100644 --- a/templates/js/ai-bot/.webappignore +++ b/templates/js/ai-bot/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/ai-bot/teamsapp.testtool.yml b/templates/js/ai-bot/teamsapp.testtool.yml index 3c7475a362..dc6c694554 100644 --- a/templates/js/ai-bot/teamsapp.testtool.yml +++ b/templates/js/ai-bot/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/api-plugin-from-scratch/README.md b/templates/js/api-plugin-from-scratch/README.md index 6462dbb278..b6814acbf5 100644 --- a/templates/js/api-plugin-from-scratch/README.md +++ b/templates/js/api-plugin-from-scratch/README.md @@ -20,7 +20,7 @@ This app template allows Microsoft Copilot for Microsoft 365 to interact directl 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. -3. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)` from the launch configuration dropdown. +3. Select `Debug in Copilot (Edge)` or `Debug in Copilot (Chrome)` from the launch configuration dropdown. 4. Send a message to Copilot to find a repair record. ## What's included in the template diff --git a/templates/js/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl b/templates/js/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl index fb11edba12..c406a2a01e 100644 --- a/templates/js/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl +++ b/templates/js/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl @@ -17,20 +17,7 @@ }, "required": [ "assignedTo" - ] - }, - "states": { - "reasoning": { - "description": "Returns the repair records.", - "instructions": [ - "Here are the parameters:", - " assignedTo: The person assigned to the repair." - ] - }, - "responding": { - "description": "Returns the repair result in JSON format.", - "instructions": "Extract and include as much relevant information as possible from the JSON result to meet the user's needs." - } + ] } } ], diff --git a/templates/js/api-plugin-from-scratch/appPackage/manifest.json.tpl b/templates/js/api-plugin-from-scratch/appPackage/manifest.json.tpl index 9ebc549abb..e345ae0488 100644 --- a/templates/js/api-plugin-from-scratch/appPackage/manifest.json.tpl +++ b/templates/js/api-plugin-from-scratch/appPackage/manifest.json.tpl @@ -25,7 +25,8 @@ "accentColor": "#FFFFFF", "plugins": [ { - "pluginFile": "ai-plugin.json" + "file": "ai-plugin.json", + "id": "plugin_1" } ], "permissions": [ diff --git a/templates/js/api-plugin-from-scratch/teamsapp.local.yml.tpl b/templates/js/api-plugin-from-scratch/teamsapp.local.yml.tpl index 09dfe6c0a4..15f1449729 100644 --- a/templates/js/api-plugin-from-scratch/teamsapp.local.yml.tpl +++ b/templates/js/api-plugin-from-scratch/teamsapp.local.yml.tpl @@ -21,12 +21,6 @@ provision: echo "::set-teamsfx-env FUNC_NAME=repair"; echo "::set-teamsfx-env FUNC_ENDPOINT=http://localhost:7071"; - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json - # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: @@ -35,12 +29,6 @@ provision: outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - # Apply the Teams app manifest to an existing Teams app in # Teams Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. diff --git a/templates/js/api-plugin-from-scratch/teamsapp.yml.tpl b/templates/js/api-plugin-from-scratch/teamsapp.yml.tpl index 774e9b23e6..a8596c4086 100644 --- a/templates/js/api-plugin-from-scratch/teamsapp.yml.tpl +++ b/templates/js/api-plugin-from-scratch/teamsapp.yml.tpl @@ -42,12 +42,6 @@ provision: # will use bicep CLI in PATH if you remove this config. bicepCliVersion: v0.9.1 - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json - # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: @@ -56,12 +50,6 @@ provision: outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - # Apply the Teams app manifest to an existing Teams app in # Teams Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. @@ -105,11 +93,6 @@ deploy: # Triggered when 'teamsapp publish' is executed publish: - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: @@ -117,11 +100,6 @@ publish: manifestPath: ./appPackage/manifest.json outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip # Apply the Teams app manifest to an existing Teams app in # Teams Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. diff --git a/templates/js/command-and-response/.appserviceIgnore b/templates/js/command-and-response/.appserviceIgnore index e734b956d0..2d8a3ad185 100644 --- a/templates/js/command-and-response/.appserviceIgnore +++ b/templates/js/command-and-response/.appserviceIgnore @@ -25,3 +25,4 @@ teamsapp.*.yml /node_modules/typescript /appPackage/ /infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/command-and-response/.gitignore b/templates/js/command-and-response/.gitignore index 92e0176ccf..dfb975ac86 100644 --- a/templates/js/command-and-response/.gitignore +++ b/templates/js/command-and-response/.gitignore @@ -22,3 +22,5 @@ lib/ .notification.localstore.json .notification.testtoolstore.json +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/js/command-and-response/teamsapp.testtool.yml b/templates/js/command-and-response/teamsapp.testtool.yml index bb912b9a9d..3217c43522 100644 --- a/templates/js/command-and-response/teamsapp.testtool.yml +++ b/templates/js/command-and-response/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/custom-copilot-assistant-assistants-api/.webappignore b/templates/js/custom-copilot-assistant-assistants-api/.webappignore index 3e9dfd4d90..1759231814 100644 --- a/templates/js/custom-copilot-assistant-assistants-api/.webappignore +++ b/templates/js/custom-copilot-assistant-assistants-api/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/custom-copilot-assistant-assistants-api/teamsapp.testtool.yml b/templates/js/custom-copilot-assistant-assistants-api/teamsapp.testtool.yml index e116319a64..123d263045 100644 --- a/templates/js/custom-copilot-assistant-assistants-api/teamsapp.testtool.yml +++ b/templates/js/custom-copilot-assistant-assistants-api/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/custom-copilot-assistant-new/.webappignore b/templates/js/custom-copilot-assistant-new/.webappignore index 3e9dfd4d90..1759231814 100644 --- a/templates/js/custom-copilot-assistant-new/.webappignore +++ b/templates/js/custom-copilot-assistant-new/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/custom-copilot-assistant-new/env/.env.dev.user.tpl b/templates/js/custom-copilot-assistant-new/env/.env.dev.user.tpl index 849946d3ca..ed67f2e2ac 100644 --- a/templates/js/custom-copilot-assistant-new/env/.env.dev.user.tpl +++ b/templates/js/custom-copilot-assistant-new/env/.env.dev.user.tpl @@ -23,5 +23,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-assistant-new/env/.env.local.user.tpl b/templates/js/custom-copilot-assistant-new/env/.env.local.user.tpl index f021ef6e7b..b1c0fc39f2 100644 --- a/templates/js/custom-copilot-assistant-new/env/.env.local.user.tpl +++ b/templates/js/custom-copilot-assistant-new/env/.env.local.user.tpl @@ -24,5 +24,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-assistant-new/env/.env.testtool.user.tpl b/templates/js/custom-copilot-assistant-new/env/.env.testtool.user.tpl index 9eec8f1cf9..8beb393d16 100644 --- a/templates/js/custom-copilot-assistant-new/env/.env.testtool.user.tpl +++ b/templates/js/custom-copilot-assistant-new/env/.env.testtool.user.tpl @@ -23,5 +23,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-assistant-new/teamsapp.testtool.yml.tpl b/templates/js/custom-copilot-assistant-new/teamsapp.testtool.yml.tpl index b520d3fff6..52954e85ca 100644 --- a/templates/js/custom-copilot-assistant-new/teamsapp.testtool.yml.tpl +++ b/templates/js/custom-copilot-assistant-new/teamsapp.testtool.yml.tpl @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/custom-copilot-basic/.gitignore b/templates/js/custom-copilot-basic/.gitignore index 1cbb2aad8d..07a76d44b3 100644 --- a/templates/js/custom-copilot-basic/.gitignore +++ b/templates/js/custom-copilot-basic/.gitignore @@ -14,3 +14,6 @@ node_modules/ .env .deployment .DS_Store + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/js/custom-copilot-basic/.webappignore b/templates/js/custom-copilot-basic/.webappignore index 18a015a2a3..f79d01ac12 100644 --- a/templates/js/custom-copilot-basic/.webappignore +++ b/templates/js/custom-copilot-basic/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/custom-copilot-basic/env/.env.dev.user.tpl b/templates/js/custom-copilot-basic/env/.env.dev.user.tpl index 849946d3ca..ed67f2e2ac 100644 --- a/templates/js/custom-copilot-basic/env/.env.dev.user.tpl +++ b/templates/js/custom-copilot-basic/env/.env.dev.user.tpl @@ -23,5 +23,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-basic/env/.env.local.user.tpl b/templates/js/custom-copilot-basic/env/.env.local.user.tpl index f021ef6e7b..b1c0fc39f2 100644 --- a/templates/js/custom-copilot-basic/env/.env.local.user.tpl +++ b/templates/js/custom-copilot-basic/env/.env.local.user.tpl @@ -24,5 +24,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-basic/env/.env.testtool.user.tpl b/templates/js/custom-copilot-basic/env/.env.testtool.user.tpl index 9eec8f1cf9..8beb393d16 100644 --- a/templates/js/custom-copilot-basic/env/.env.testtool.user.tpl +++ b/templates/js/custom-copilot-basic/env/.env.testtool.user.tpl @@ -23,5 +23,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-basic/teamsapp.testtool.yml.tpl b/templates/js/custom-copilot-basic/teamsapp.testtool.yml.tpl index b520d3fff6..52954e85ca 100644 --- a/templates/js/custom-copilot-basic/teamsapp.testtool.yml.tpl +++ b/templates/js/custom-copilot-basic/teamsapp.testtool.yml.tpl @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/custom-copilot-rag-azure-ai-search/.gitignore b/templates/js/custom-copilot-rag-azure-ai-search/.gitignore new file mode 100644 index 0000000000..8bf43ee6fa --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/.gitignore @@ -0,0 +1,22 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +.localConfigs +.localConfigs.testTool +.notification.localstore.json +.notification.testtoolstore.json +appPackage/build + +# dependencies +node_modules/ + +# misc +.env +.deployment +.DS_Store + +# build +lib/ + +# devTools +devTools/ diff --git a/templates/js/custom-copilot-rag-azure-ai-search/.localConfigs.testTool.tpl b/templates/js/custom-copilot-rag-azure-ai-search/.localConfigs.testTool.tpl new file mode 100644 index 0000000000..1cd7e4b6d7 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/.localConfigs.testTool.tpl @@ -0,0 +1,15 @@ +# A gitignored place holder file for local runtime configurations +BOT_ID= +BOT_PASSWORD= +{{#useOpenAI}} +OPENAI_API_KEY= +{{/useOpenAI}} +{{#useAzureOpenAI}} +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME= +{{/useAzureOpenAI}} +AZURE_SEARCH_KEY= +AZURE_SEARCH_ENDPOINT= +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json diff --git a/templates/js/custom-copilot-rag-azure-ai-search/.localConfigs.tpl b/templates/js/custom-copilot-rag-azure-ai-search/.localConfigs.tpl new file mode 100644 index 0000000000..dee531d6a4 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/.localConfigs.tpl @@ -0,0 +1,14 @@ +# A gitignored place holder file for local runtime configurations +BOT_ID= +BOT_PASSWORD= +{{#useOpenAI}} +OPENAI_API_KEY= +{{/useOpenAI}} +{{#useAzureOpenAI}} +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME= +{{/useAzureOpenAI}} +AZURE_SEARCH_KEY= +AZURE_SEARCH_ENDPOINT= \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/.vscode/extensions.json b/templates/js/custom-copilot-rag-azure-ai-search/.vscode/extensions.json new file mode 100644 index 0000000000..1b70a39308 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension" + ] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/.vscode/launch.json.tpl b/templates/js/custom-copilot-rag-azure-ai-search/.vscode/launch.json.tpl new file mode 100644 index 0000000000..9e3b45ee1f --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/.vscode/launch.json.tpl @@ -0,0 +1,122 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Attach to Local Service", + "type": "node", + "request": "attach", + "port": 9239, + "restart": true, + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Teams (Edge)", + "configurations": [ + "Launch App (Edge)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { +{{#enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Chrome)", + "configurations": [ + "Launch App (Chrome)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { +{{#enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} + "order": 2 + }, + "stopAll": true + }, + { + "name": "Debug in Test Tool (Preview)", + "configurations": [ + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App (Test Tool)", + "presentation": { +{{#enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} + "order": 1 + }, + "stopAll": true + } + ] +} diff --git a/templates/js/custom-copilot-rag-azure-ai-search/.vscode/settings.json b/templates/js/custom-copilot-rag-azure-ai-search/.vscode/settings.json new file mode 100644 index 0000000000..0d3ba10b02 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/.vscode/tasks.json b/templates/js/custom-copilot-rag-azure-ai-search/.vscode/tasks.json new file mode 100644 index 0000000000..1c3e241f27 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/.vscode/tasks.json @@ -0,0 +1,204 @@ +// This file is automatically generated by Teams Toolkit. +// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0. +// See https://aka.ms/teamsfx-tasks for details on how to customize each task. +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Teams App (Test Tool)", + "dependsOn": [ + "Validate prerequisites (Test Tool)", + "Deploy (Test Tool)", + "Start application (Test Tool)", + "Start Test Tool", + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites (Test Tool)", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239, // app inspector port for Node.js debugger + 56150, // test tool port + ] + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy (Test Tool)", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "testtool", + } + }, + { + "label": "Start application (Test Tool)", + "type": "shell", + "command": "npm run dev:teamsfx:testtool", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}", + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + }, + { + "label": "Start Test Tool", + "type": "shell", + "command": "npm run dev:teamsfx:launch-testtool", + "isBackground": true, + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin:${env:PATH}" + } + }, + "windows": { + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin;${env:PATH}" + } + } + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Listening on" + } + }, + "presentation": { + "panel": "dedicated", + "reveal": "silent" + } + }, + { + "label": "Start Teams App Locally", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Provision", + "Deploy", + "Start application" + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239 // app inspector port for Node.js debugger + ] + } + }, + { + // Start the local tunnel service to forward public URL to local port and inspect traffic. + // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. + "label": "Start local tunnel", + "type": "teamsfx", + "command": "debug-start-local-tunnel", + "args": { + "type": "dev-tunnel", + "ports": [ + { + "portNumber": 3978, + "protocol": "http", + "access": "public", + "writeToEnvironmentFile": { + "endpoint": "BOT_ENDPOINT", // output tunnel endpoint as BOT_ENDPOINT + "domain": "BOT_DOMAIN" // output tunnel domain as BOT_DOMAIN + } + } + ], + "env": "local" + }, + "isBackground": true, + "problemMatcher": "$teamsfx-local-tunnel-watch" + }, + { + // Create the debug resources. + // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args. + "label": "Provision", + "type": "teamsfx", + "command": "provision", + "args": { + "env": "local" + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "local" + } + }, + { + "label": "Start application", + "type": "shell", + "command": "npm run dev:teamsfx", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + } + ] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/.webappignore b/templates/js/custom-copilot-rag-azure-ai-search/.webappignore new file mode 100644 index 0000000000..f79d01ac12 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/.webappignore @@ -0,0 +1,28 @@ +.webappignore +.fx +.deployment +.localConfigs.testTool +.localConfigs +.notification.localstore.json +.notification.testtoolstore.json +.vscode +*.js.map +*.ts.map +*.ts +.git* +.tsbuildinfo +CHANGELOG.md +readme.md +local.settings.json +test +tsconfig.json +.DS_Store +teamsapp.yml +teamsapp.*.yml +/env/ +/node_modules/.bin +/node_modules/ts-node +/node_modules/typescript +/appPackage/ +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/README.md.tpl b/templates/js/custom-copilot-rag-azure-ai-search/README.md.tpl new file mode 100644 index 0000000000..99f3749670 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/README.md.tpl @@ -0,0 +1,90 @@ +# Overview of the AI Search Bot template + +This app template is built on top of [Teams AI library](https://aka.ms/teams-ai-library). +It showcases how to build an basic RAG bot in Teams capable of chatting with users but with context provided by Azure AI Search data source. + +- [Overview of the AI Search Bot template](#overview-of-the-ai-search-bot-template) + - [Get started with the AI Search Bot template](#get-started-with-the-ai-search-bot-template) + - [What's included in the template](#whats-included-in-the-template) + - [Extend the AI Search Bot template with more AI capabilities](#extend-the-ai-search-bot-template-with-more-ai-capabilities) + - [Additional information and references](#additional-information-and-references) + +## Get started with the AI Search Bot template + +> **Prerequisites** +> +> To run the AI Search bot template in your local dev machine, you will need: +> +> - [Node.js](https://nodejs.org/), supported versions: 16, 18 +> - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +{{#useOpenAI}} +> - An account with [OpenAI](https://platform.openai.com/) and [Azure AI Search](https://azure.microsoft.com/en-us/products/ai-services/ai-search). +{{/useOpenAI}} +{{#useAzureOpenAI}} +> - Prepare your own [Azure OpenAI](https://aka.ms/oai/access) resource and [Azure AI Search](https://azure.microsoft.com/en-us/products/ai-services/ai-search). +{{/useAzureOpenAI}} + +1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#useOpenAI}} +1. In file *env/.env.testtool.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=`. And fill in your Azure AI search key `SECRET_AZURE_SEARCH_KEY=` and endpoint `AZURE_SEARCH_ENDPOINT=`. +{{/useOpenAI}} +{{#useAzureOpenAI}} +1. In file *env/.env.testtool.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_API_KEY=`, endpoint `AZURE_OPENAI_ENDPOINT=`, deployment name `AZURE_OPENAI_DEPLOYMENT_NAME=`, and embedding deployment name `AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=`. And fill in your Azure AI search key `SECRET_AZURE_SEARCH_KEY=` and endpoint `AZURE_SEARCH_ENDPOINT=`. +{{/useAzureOpenAI}} +1. Do `npm install` and `npm run indexer:create` to create the my documents index. Once you're done using the sample it's good practice to delete the index. You can do so with the `npm run indexer:delete` command. +1. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. Select `Debug in Test Tool (Preview)`. +1. You can send any message to get a response from the bot. + +**Congratulations**! You are running an application that can now interact with users in Teams App Test Tool: + +![AI Search Bot](https://github.com/OfficeDev/TeamsFx/assets/13211513/f56e7602-a5d3-436a-ae01-78546d61717d) + +## What's included in the template + +| Folder | Contents | +| - | - | +| `.vscode` | VSCode files for debugging | +| `appPackage` | Templates for the Teams application manifest | +| `env` | Environment files | +| `infra` | Templates for provisioning Azure resources | +| `src` | The source code for the application | + +The following files can be customized and demonstrate an example implementation to get you started. + +| File | Contents | +| - | - | +|`src/index.js`| Sets up the bot app server.| +|`src/adapter.js`| Sets up the bot adapter.| +|`src/config.js`| Defines the environment variables.| +|`src/prompts/chat/skprompt.txt`| Defines the prompt.| +|`src/prompts/chat/config.json`| Configures the prompt.| +|`src/app/app.js`| Handles business logics for the RAG bot.| +|`src/app/azureAISearchDataSource.js`| Defines the Azure AI search data source.| +|`src/indexers/data/*.md`| Raw text data sources.| +|`src/indexers/utils.js`| Basic index tools. | +|`src/indexers/setup.js`| A script to create index and upload documents. | +|`src/indexers/delete.js`| A script to delete index and documents. | + +The following are Teams Toolkit specific project files. You can [visit a complete guide on Github](https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview) to understand how Teams Toolkit works. + +| File | Contents | +| - | - | +|`teamsapp.yml`|This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | +|`teamsapp.local.yml`|This overrides `teamsapp.yml` with actions that enable local execution and debugging.| +|`teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool.| + +## Extend the AI Search bot template with more AI capabilities + +You can follow [Build a Basic AI Chatbot in Teams](https://aka.ms/teamsfx-basic-ai-chatbot) to extend the Basic AI Chatbot template with more AI capabilities, like: +- [Customize prompt](https://aka.ms/teamsfx-basic-ai-chatbot#customize-prompt) +- [Customize user input](https://aka.ms/teamsfx-basic-ai-chatbot#customize-user-input) +- [Customize conversation history](https://aka.ms/teamsfx-basic-ai-chatbot#customize-conversation-history) +- [Customize model type](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-type) +- [Customize model parameters](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-parameters) +- [Handle messages with image](https://aka.ms/teamsfx-basic-ai-chatbot#handle-messages-with-image) + +## Additional information and references +- [Teams AI library](https://aka.ms/teams-ai-library) +- [Teams Toolkit Documentations](https://docs.microsoft.com/microsoftteams/platform/toolkit/teams-toolkit-fundamentals) +- [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +- [Teams Toolkit Samples](https://github.com/OfficeDev/TeamsFx-Samples) \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/appPackage/color.png b/templates/js/custom-copilot-rag-azure-ai-search/appPackage/color.png new file mode 100644 index 0000000000..2d7e85c9e9 Binary files /dev/null and b/templates/js/custom-copilot-rag-azure-ai-search/appPackage/color.png differ diff --git a/templates/js/custom-copilot-rag-azure-ai-search/appPackage/manifest.json.tpl b/templates/js/custom-copilot-rag-azure-ai-search/appPackage/manifest.json.tpl new file mode 100644 index 0000000000..d7a51bc8fb --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/appPackage/manifest.json.tpl @@ -0,0 +1,46 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", + "manifestVersion": "1.16", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.microsoft.teams.extension", + "developer": { + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/privacy", + "termsOfUseUrl": "https://www.example.com/termofuse" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "{{appName}}${{APP_NAME_SUFFIX}}", + "full": "full name for {{appName}}" + }, + "description": { + "short": "short description for {{appName}}", + "full": "full description for {{appName}}" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "${{BOT_ID}}", + "scopes": [ + "personal", + "team", + "groupchat" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "composeExtensions": [], + "configurableTabs": [], + "staticTabs": [], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/appPackage/outline.png b/templates/js/custom-copilot-rag-azure-ai-search/appPackage/outline.png new file mode 100644 index 0000000000..245fa194db Binary files /dev/null and b/templates/js/custom-copilot-rag-azure-ai-search/appPackage/outline.png differ diff --git a/templates/js/custom-copilot-rag-azure-ai-search/env/.env.dev b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.dev new file mode 100644 index 0000000000..4b07861c03 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.dev @@ -0,0 +1,16 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/env/.env.dev.user.tpl b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.dev.user.tpl new file mode 100644 index 0000000000..0a35e03f50 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.dev.user.tpl @@ -0,0 +1,50 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{#azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME='{{{azureOpenAIEmbeddingDeploymentName}}}' +{{/azureOpenAIEmbeddingDeploymentName}} +{{^azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME= +{{/azureOpenAIEmbeddingDeploymentName}} +{{/useAzureOpenAI}} +{{#secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY='{{{secretAzureSearchKey}}}' +{{/secretAzureSearchKey}} +{{^secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY= +{{/secretAzureSearchKey}} +{{#azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT='{{{azureSearchEndpoint}}}' +{{/azureSearchEndpoint}} +{{^azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT= +{{/azureSearchEndpoint}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/env/.env.local b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.local new file mode 100644 index 0000000000..f3a75f8723 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.local @@ -0,0 +1,11 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=local +APP_NAME_SUFFIX=local + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_DOMAIN= +BOT_ENDPOINT= \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/env/.env.local.user.tpl b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.local.user.tpl new file mode 100644 index 0000000000..13f3ff0b84 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.local.user.tpl @@ -0,0 +1,51 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{#azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME='{{{azureOpenAIEmbeddingDeploymentName}}}' +{{/azureOpenAIEmbeddingDeploymentName}} +{{^azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME= +{{/azureOpenAIEmbeddingDeploymentName}} +{{/useAzureOpenAI}} +{{#secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY='{{{secretAzureSearchKey}}}' +{{/secretAzureSearchKey}} +{{^secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY= +{{/secretAzureSearchKey}} +{{#azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT='{{{azureSearchEndpoint}}}' +{{/azureSearchEndpoint}} +{{^azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT= +{{/azureSearchEndpoint}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/env/.env.testtool b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.testtool new file mode 100644 index 0000000000..43ce12aad3 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.testtool @@ -0,0 +1,8 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=testtool + +# Environment variables used by test tool +TEAMSAPPTESTER_PORT=56150 +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json diff --git a/templates/js/custom-copilot-rag-azure-ai-search/env/.env.testtool.user.tpl b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.testtool.user.tpl new file mode 100644 index 0000000000..744a906306 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/env/.env.testtool.user.tpl @@ -0,0 +1,50 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{#azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME='{{{azureOpenAIEmbeddingDeploymentName}}}' +{{/azureOpenAIEmbeddingDeploymentName}} +{{^azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME= +{{/azureOpenAIEmbeddingDeploymentName}} +{{/useAzureOpenAI}} +{{#secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY='{{{secretAzureSearchKey}}}' +{{/secretAzureSearchKey}} +{{^secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY= +{{/secretAzureSearchKey}} +{{#azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT='{{{azureSearchEndpoint}}}' +{{/azureSearchEndpoint}} +{{^azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT= +{{/azureSearchEndpoint}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/infra/azure.bicep.tpl b/templates/js/custom-copilot-rag-azure-ai-search/infra/azure.bicep.tpl new file mode 100644 index 0000000000..9cb0635756 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/infra/azure.bicep.tpl @@ -0,0 +1,138 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@description('Required when create Azure Bot service') +param botAadAppClientId string + +@secure() +@description('Required by Bot Framework package in your bot project') +param botAadAppClientSecret string + +{{#useOpenAI}} +@secure() +param openAIKey string +{{/useOpenAI}} +{{#useAzureOpenAI}} +@secure() +param azureOpenAIKey string + +@secure() +param azureOpenAIEndpoint string + +@secure() +param azureOpenAIDeploymentName string + +@secure() +param azureOpenAIEmbeddingDeploymentName string +{{/useAzureOpenAI}} + +@secure() +param azureSearchKey string + +@secure() +param azureSearchEndpoint string + +param webAppSKU string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param location string = resourceGroup().location + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + alwaysOn: true + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' // Run Azure App Service from a package file + } + { + name: 'WEBSITE_NODE_DEFAULT_VERSION' + value: '~18' // Set NodeJS version to 18.x for your site + } + { + name: 'RUNNING_ON_AZURE' + value: '1' + } + { + name: 'BOT_ID' + value: botAadAppClientId + } + { + name: 'BOT_PASSWORD' + value: botAadAppClientSecret + } + {{#useOpenAI}} + { + name: 'OPENAI_API_KEY' + value: openAIKey + } + {{/useOpenAI}} + {{#useAzureOpenAI}} + { + name: 'AZURE_OPENAI_API_KEY' + value: azureOpenAIKey + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: azureOpenAIEndpoint + } + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME' + value: azureOpenAIDeploymentName + } + { + name: 'AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME' + value: azureOpenAIEmbeddingDeploymentName + } + {{/useAzureOpenAI}} + { + name: 'AZURE_SEARCH_KEY' + value: azureSearchKey + } + { + name: 'AZURE_SEARCH_ENDPOINT' + value: azureSearchEndpoint + } + ] + ftpsState: 'FtpsOnly' + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + botAadAppClientId: botAadAppClientId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/templates/js/custom-copilot-rag-azure-ai-search/infra/azure.parameters.json.tpl b/templates/js/custom-copilot-rag-azure-ai-search/infra/azure.parameters.json.tpl new file mode 100644 index 0000000000..47b691a8de --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/infra/azure.parameters.json.tpl @@ -0,0 +1,46 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "botAadAppClientId": { + "value": "${{BOT_ID}}" + }, + "botAadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + {{#useOpenAI}} + "openAIKey": { + "value": "${{SECRET_OPENAI_API_KEY}}" + }, + {{/useOpenAI}} + {{#useAzureOpenAI}} + "azureOpenAIKey": { + "value": "${{SECRET_AZURE_OPENAI_API_KEY}}" + }, + "azureOpenAIEndpoint": { + "value": "${{AZURE_OPENAI_ENDPOINT}}" + }, + "azureOpenAIDeploymentName": { + "value": "${{AZURE_OPENAI_DEPLOYMENT_NAME}}" + }, + "azureOpenAIEmbeddingDeploymentName": { + "value": "${{AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME}}" + }, + {{/useAzureOpenAI}} + "azureSearchKey": { + "value": "${{SECRET_AZURE_SEARCH_KEY}}" + }, + "azureSearchEndpoint": { + "value": "${{AZURE_SEARCH_ENDPOINT}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "{{appName}}" + } + } +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/infra/botRegistration/azurebot.bicep b/templates/js/custom-copilot-rag-azure-ai-search/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..ab67c7a56b --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/infra/botRegistration/azurebot.bicep @@ -0,0 +1,37 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param botAadAppClientId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: botAadAppClientId + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/templates/js/custom-copilot-rag-azure-ai-search/infra/botRegistration/readme.md b/templates/js/custom-copilot-rag-azure-ai-search/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/package.json.tpl b/templates/js/custom-copilot-rag-azure-ai-search/package.json.tpl new file mode 100644 index 0000000000..dc442456d7 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/package.json.tpl @@ -0,0 +1,40 @@ +{ + "name": "{{SafeProjectNameLowerCase}}", + "version": "1.0.0", + "msteams": { + "teamsAppId": null + }, + "description": "Microsoft Teams Toolkit RAG Bot Sample with Azure AI Search and Teams AI Library", + "engines": { + "node": "16 || 18" + }, + "author": "Microsoft", + "license": "MIT", + "main": "./src/index.js", + "scripts": { + "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", + "dev:teamsfx:testtool": "env-cmd --silent -f .localConfigs.testTool npm run dev", + "dev:teamsfx:launch-testtool": "env-cmd --silent -f env/.env.testtool teamsapptester start", + "dev": "nodemon --inspect=9239 --signal SIGINT ./src/index.js", + "start": "node ./src/index.js", + "test": "echo \"Error: no test specified\" && exit 1", + "watch": "nodemon --exec \"npm run start\"", + "indexer:create": "env-cmd --silent -f env/.env.testtool.user node ./src/indexers/setup.js", + "indexer:delete": "env-cmd --silent -f env/.env.testtool.user node ./src/indexers/delete.js" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "dependencies": { + "@azure/search-documents": "^12.0.0", + "@microsoft/teams-ai": "^1.1.0", + "botbuilder": "^4.20.0", + "openai": "~4.28.4", + "restify": "^10.0.0" + }, + "devDependencies": { + "env-cmd": "^10.1.0", + "nodemon": "^2.0.7" + } +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/adapter.js b/templates/js/custom-copilot-rag-azure-ai-search/src/adapter.js new file mode 100644 index 0000000000..c8fdf6f07c --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/adapter.js @@ -0,0 +1,51 @@ +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +const { + CloudAdapter, + ConfigurationServiceClientCredentialFactory, + ConfigurationBotFrameworkAuthentication, +} = require("botbuilder"); + +// This bot's main dialog. +const config = require("./config"); + +const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication( + {}, + new ConfigurationServiceClientCredentialFactory({ + MicrosoftAppId: config.botId, + MicrosoftAppPassword: process.env.BOT_PASSWORD, + MicrosoftAppType: "MultiTenant", + }) +); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about how bots work. +const adapter = new CloudAdapter(botFrameworkAuthentication); + +// Catch-all for errors. +const onTurnErrorHandler = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. + console.error(`\n [onTurnError] unhandled error: ${error}`); + + // Only send error message for user messages, not for other message types so the bot doesn't spam a channel or chat. + if (context.activity.type === "message") { + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendTraceActivity( + "OnTurnError Trace", + `${error}`, + "https://www.botframework.com/schemas/error", + "TurnError" + ); + + // Send a message to the user + await context.sendActivity("The bot encountered an error or bug."); + await context.sendActivity("To continue to run this bot, please fix the bot source code."); + } +}; + +// Set the onTurnError for the singleton CloudAdapter. +adapter.onTurnError = onTurnErrorHandler; + +module.exports = adapter; diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/app/app.js.tpl b/templates/js/custom-copilot-rag-azure-ai-search/src/app/app.js.tpl new file mode 100644 index 0000000000..87cd20cc66 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/app/app.js.tpl @@ -0,0 +1,61 @@ +const { MemoryStorage } = require("botbuilder"); +const path = require("path"); +const config = require("../config"); + +// See https://aka.ms/teams-ai-library to learn more about the Teams AI library. +const { Application, ActionPlanner, OpenAIModel, PromptManager } = require("@microsoft/teams-ai"); +const { AzureAISearchDataSource } = require("./azureAISearchDataSource"); + +// Create AI components +const model = new OpenAIModel({ + {{#useOpenAI}} + apiKey: config.openAIKey, + defaultModel: config.openAIModelName, + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureApiKey: config.azureOpenAIKey, + azureDefaultDeployment: config.azureOpenAIDeploymentName, + azureEndpoint: config.azureOpenAIEndpoint, + {{/useAzureOpenAI}} + + useSystemMessages: true, + logRequests: true, +}); +const prompts = new PromptManager({ + promptsFolder: path.join(__dirname, "../prompts"), +}); +const planner = new ActionPlanner({ + model, + prompts, + defaultPrompt: "chat", +}); + +// Register your data source with planner +planner.prompts.addDataSource( + new AzureAISearchDataSource({ + name: "azure-ai-search", + indexName: "my-documents", + azureAISearchApiKey: config.azureSearchKey, + azureAISearchEndpoint: config.azureSearchEndpoint, + {{#useOpenAI}} + apiKey: config.openAIKey, + openAIEmbeddingModelName: config.openAIEmbeddingModelName, + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureOpenAIApiKey: config.azureOpenAIKey, + azureOpenAIEndpoint: config.azureOpenAIEndpoint, + azureOpenAIEmbeddingDeploymentName: config.azureOpenAIEmbeddingDeploymentName, + {{/useAzureOpenAI}} + }) +); + +// Define storage and application +const storage = new MemoryStorage(); +const app = new Application({ + storage, + ai: { + planner, + }, +}); + +module.exports = app; diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/app/azureAISearchDataSource.js.tpl b/templates/js/custom-copilot-rag-azure-ai-search/src/app/azureAISearchDataSource.js.tpl new file mode 100644 index 0000000000..7a9d3d506c --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/app/azureAISearchDataSource.js.tpl @@ -0,0 +1,116 @@ +const { OpenAIEmbeddings } = require("@microsoft/teams-ai"); +const { AzureKeyCredential, SearchClient } = require("@azure/search-documents"); + +/** + * A data source that searches through Azure AI search. + */ +class AzureAISearchDataSource { + /** + * Creates a new `AzureAISearchDataSource` instance. + */ + constructor(options) { + this.name = options.name; + this.options = options; + this.searchClient = new SearchClient( + options.azureAISearchEndpoint, + options.indexName, + new AzureKeyCredential(options.azureAISearchApiKey), + {} + ); + } + + /** + * Renders the data source as a string of text. + */ + async renderData(context, memory, tokenizer, maxTokens) { + const query = memory.getValue("temp.input"); + if(!query) { + return { output: "", length: 0, tooLong: false }; + } + + const selectedFields = [ + "docId", + "docTitle", + "description", + ]; + + // hybrid search + const queryVector= await this.getEmbeddingVector(query); + const searchResults = await this.searchClient.search(query, { + searchFields: ["docTitle", "description"], + select: selectedFields, + vectorSearchOptions: { + queries: [ + { + kind: "vector", + fields: ["descriptionVector"], + kNearestNeighborsCount: 2, + // The query vector is the embedding of the user's input + vector: queryVector + } + ] + }, + }); + + if (!searchResults.results) { + return { output: "", length: 0, tooLong: false }; + } + + // Concatenate the documents string into a single document + // until the maximum token limit is reached. This can be specified in the prompt template. + let usedTokens = 0; + let doc = ""; + for await (const result of searchResults.results) { + const formattedResult = this.formatDocument(result.document.description); + const tokens = tokenizer.encode(formattedResult).length; + + if (usedTokens + tokens > maxTokens) { + break; + } + + doc += formattedResult; + usedTokens += tokens; + } + + return { output: doc, length: usedTokens, tooLong: usedTokens > maxTokens }; + } + + /** + * Formats the result string + */ + formatDocument(result) { + return `${result}`; + } + + /** + * Generate embeddings for the user's input. + */ + async getEmbeddingVector(text) { + {{#useOpenAI}} + const embeddings = new OpenAIEmbeddings({ + apiKey: this.options.apiKey, + model: this.options.openAIEmbeddingModelName, + }); + const result = await embeddings.createEmbeddings(this.options.openAIEmbeddingModelName, text); + {{/useOpenAI}} + {{#useAzureOpenAI}} + const embeddings = new OpenAIEmbeddings({ + azureApiKey: this.options.azureOpenAIApiKey, + azureEndpoint: this.options.azureOpenAIEndpoint, + azureDeployment: this.options.azureOpenAIEmbeddingDeploymentName, + }); + + const result = await embeddings.createEmbeddings(this.options.azureOpenAIEmbeddingDeploymentName, text); + {{/useAzureOpenAI}} + + if (result.status !== "success" || !result.output) { + throw new Error(`Failed to generate embeddings for description: ${text}`); + } + + return result.output[0]; + } +} + +module.exports = { + AzureAISearchDataSource, +}; \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/config.js.tpl b/templates/js/custom-copilot-rag-azure-ai-search/src/config.js.tpl new file mode 100644 index 0000000000..9ffe0f9f01 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/config.js.tpl @@ -0,0 +1,19 @@ +const config = { + botId: process.env.BOT_ID, + botPassword: process.env.BOT_PASSWORD, + {{#useOpenAI}} + openAIKey: process.env.OPENAI_API_KEY, + openAIModelName: "gpt-3.5-turbo", + openAIEmbeddingModelName: "text-embedding-ada-002", + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureOpenAIKey: process.env.AZURE_OPENAI_API_KEY, + azureOpenAIEndpoint: process.env.AZURE_OPENAI_ENDPOINT, + azureOpenAIDeploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME, + azureOpenAIEmbeddingDeploymentName: process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME, + {{/useAzureOpenAI}} + azureSearchKey: process.env.AZURE_SEARCH_KEY, + azureSearchEndpoint: process.env.AZURE_SEARCH_ENDPOINT, +}; + +module.exports = config; diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/index.js b/templates/js/custom-copilot-rag-azure-ai-search/src/index.js new file mode 100644 index 0000000000..d1e686f00d --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/index.js @@ -0,0 +1,25 @@ +// Import required packages +const restify = require("restify"); + +// This bot's adapter +const adapter = require("./adapter"); + +// This bot's main dialog. +const app = require("./app/app"); + +// Create HTTP server. +const server = restify.createServer(); +server.use(restify.plugins.bodyParser()); + +server.listen(process.env.port || process.env.PORT || 3978, () => { + console.log(`\nBot Started, ${server.name} listening to ${server.url}`); +}); + +// Listen for incoming server requests. +server.post("/api/messages", async (req, res) => { + // Route received a request to adapter for processing + await adapter.process(req, res, async (context) => { + // Dispatch to application for routing + await app.run(context); + }); +}); diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso Electronics_PerkPlus_Program.md b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso Electronics_PerkPlus_Program.md new file mode 100644 index 0000000000..1d97d5117e --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso Electronics_PerkPlus_Program.md @@ -0,0 +1,36 @@ +# Contoso Electronics PerksPlus Program + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Overview +Introducing PerksPlus - the ultimate benefits program designed to support the health and wellness of employees. With PerksPlus, employees have the opportunity to expense up to $1000 for fitness-related programs, making it easier and more affordable to maintain a healthy lifestyle. PerksPlus is not only designed to support employees' physical health, but also their mental health. Regular exercise has been shown to reduce stress, improve mood, and enhance overall well-being. With PerksPlus, employees can invest in their health and wellness, while enjoying the peace of mind that comes with knowing they are getting the support they need to lead a healthy life. +What is Covered? + +PerksPlus covers a wide range of fitness activities, including but not limited to: +* Gym memberships +* Personal training sessions +* Yoga and Pilates classes +* Fitness equipment purchases +* Sports team fees +* Health retreats and spas +* Outdoor adventure activities (such as rock climbing, hiking, and kayaking) +* Group fitness classes (such as dance, martial arts, and cycling) +* Virtual fitness programs (such as online yoga and workout classes) + +In addition to the wide range of fitness activities covered by PerksPlus, the program also covers a variety of lessons and experiences that promote health and wellness. Some of the lessons covered under PerksPlus include: +* Skiing and snowboarding lessons +* Scuba diving lessons +* Surfing lessons +* Horseback riding lessons + +These lessons provide employees with the opportunity to try new things, challenge themselves, and improve their physical skills. They are also a great way to relieve stress and have fun while staying active. + +With PerksPlus, employees can choose from a variety of fitness programs to suit their individual needs and preferences. Whether you're looking to improve your physical fitness, reduce stress, or just have some fun, PerksPlus has you covered. + +## What is Not Covered? +In addition to the wide range of activities covered by PerksPlus, there is also a list of things that are not +covered under the program. These include but are not limited to: +* Non-fitness related expenses +* Medical treatments and procedures +* Travel expenses (unless related to a fitness program) +* Food and supplements \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Company_Overview.md b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Company_Overview.md new file mode 100644 index 0000000000..6878a8e204 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Company_Overview.md @@ -0,0 +1,48 @@ +# Contoso Electronics Company Overview + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## History + +Contoso Electronics, a pioneering force in the tech industry, was founded in 1985 by visionary entrepreneurs with a passion for innovation. Over the years, the company has played a pivotal role in shaping the landscape of consumer electronics. + +| Year | Milestone | +|------|-----------| +| 1985 | Company founded with a focus on cutting-edge technology | +| 1990 | Launched the first-ever handheld personal computer | +| 2000 | Introduced groundbreaking advancements in AI and robotics | +| 2015 | Expansion into sustainable and eco-friendly product lines | + +## Company Overview + +At Contoso Electronics, we take pride in fostering a dynamic and inclusive workplace. Our dedicated team of experts collaborates to create innovative solutions that empower and connect people globally. + +### Core Values + +- **Innovation:** Constantly pushing the boundaries of technology. +- **Diversity:** Embracing different perspectives for creative excellence. +- **Sustainability:** Committed to eco-friendly practices in our products. + +## Vacation Perks + +We believe in work-life balance and understand the importance of well-deserved breaks. Our vacation perks are designed to help our employees recharge and return with renewed enthusiasm. + +| Vacation Tier | Duration | Additional Benefits | +|---------------|----------|---------------------| +| Standard | 2 weeks | Health and wellness stipend | +| Senior | 4 weeks | Travel vouchers for a dream destination | +| Executive | 6 weeks | Luxury resort getaway with family | + +## Employee Recognition + +Recognizing the hard work and dedication of our employees is at the core of our culture. Here are some ways we celebrate achievements: + +- Monthly "Innovator of the Month" awards +- Annual gala with awards for outstanding contributions +- Team-building retreats for high-performing departments + +## Join Us! + +Contoso Electronics is always on the lookout for talented individuals who share our passion for innovation. If you're ready to be part of a dynamic team shaping the future of technology, check out our [careers page](http://www.contoso.com) for exciting opportunities. + +[Learn more about Contoso Electronics!](http://www.contoso.com) diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Plan_Benefits.md b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Plan_Benefits.md new file mode 100644 index 0000000000..9da5c6429d --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Plan_Benefits.md @@ -0,0 +1,37 @@ +# Contoso Electronics Plan and Benefit Packages + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Northwind Health Plus + +Northwind Health Plus is a comprehensive plan that provides comprehensive coverage for medical, vision, and dental services. This plan also offers prescription drug coverage, mental health and substance abuse coverage, and coverage for preventive care services. With Northwind Health Plus, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. + +This plan also offers coverage for emergency services, both in-network and out-of-network. + +## Northwind Standard + +Northwind Standard is a basic plan that provides coverage for medical, vision, and dental services. This plan also offers coverage for preventive care services, as well as prescription drug coverage. With Northwind Standard, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. This plan does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +## Comparison of Plans + +Both plans offer coverage for routine physicals, well-child visits, immunizations, and other preventive care services. The plans also cover preventive care services such as mammograms, colonoscopies, and other cancer screenings. + +Northwind Health Plus offers more comprehensive coverage than Northwind Standard. This plan offers coverage for emergency services, both in-network and out-of-network, as well as mental health and substance abuse coverage. Northwind Standard does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +Both plans offer coverage for prescription drugs. Northwind Health Plus offers a wider range of prescription drug coverage than Northwind Standard. Northwind Health Plus covers generic, brand-name, and specialty drugs, while Northwind Standard only covers generic and brand-name drugs. + +Both plans offer coverage for vision and dental services. Northwind Health Plus offers coverage for vision exams, glasses, and contact lenses, as well as dental exams, cleanings, and fillings. Northwind Standard only offers coverage for vision exams and glasses. + +Both plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays, doctor visits, lab tests, and X-rays. Northwind Standard only offers coverage for doctor visits and lab tests. + +Northwind Health Plus is a comprehensive plan that offers more coverage than Northwind Standard. Northwind Health Plus offers coverage for emergency services, mental health and substance abuse coverage, and out-of-network services, while Northwind Standard does not. Northwind Health Plus also offers a wider range of prescription drug coverage than Northwind Standard. Both plans offer coverage for vision and dental services, as well as medical services. + +## Cost Comparison + +Contoso Electronics deducts the employee's portion of the healthcare cost from each paycheck. This means that the cost of the health insurance will be spread out over the course of the year, rather than being paid in one lump sum. The employee's portion of the cost will be calculated based on the selected health plan and the number of people covered by the insurance. The table below shows a cost comparison between the different health plans offered by Contoso Electronics + +| | Northwind Standard | NorthWind Health Plus | +|---------------|----------|---------------------| +| Employee Only | $45.00 | $55.00 | +| Employee +1 | $65.00 | $71.00 | +| Employee +2 or more | $78.00 | $89.00 | \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/delete.js b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/delete.js new file mode 100644 index 0000000000..48215cacd3 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/delete.js @@ -0,0 +1,10 @@ +const { AzureKeyCredential, SearchIndexClient } = require("@azure/search-documents"); +const { deleteIndex } = require("./utils"); + +const index = "my-documents"; +const searchApiKey = process.env.SECRET_AZURE_SEARCH_KEY; +const searchApiEndpoint = process.env.AZURE_SEARCH_ENDPOINT; +const credentials = new AzureKeyCredential(searchApiKey); + +const searchIndexClient = new SearchIndexClient(searchApiEndpoint, credentials); +deleteIndex(searchIndexClient, index); diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/setup.js.tpl b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/setup.js.tpl new file mode 100644 index 0000000000..b75d9b1446 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/setup.js.tpl @@ -0,0 +1,64 @@ +const { AzureKeyCredential, SearchClient, SearchIndexClient } = require("@azure/search-documents"); +const { createIndexIfNotExists, delay, upsertDocuments, getEmbeddingVector } = require("./utils"); +const path = require("path"); +const fs = require("fs"); + +/** + * Main function that creates the index and upserts the documents. + */ +async function main() { + const index = "my-documents"; + + if ( + !process.env.SECRET_AZURE_SEARCH_KEY || + !process.env.AZURE_SEARCH_ENDPOINT || + {{#useOpenAI}} + !process.env.SECRET_OPENAI_API_KEY + {{/useOpenAI}} + {{#useAzureOpenAI}} + !process.env.SECRET_AZURE_OPENAI_API_KEY || + !process.env.AZURE_OPENAI_ENDPOINT || + !process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME + {{/useAzureOpenAI}} + ) { + {{#useOpenAI}} + throw new Error( + "Missing environment variables - please check that SECRET_AZURE_SEARCH_KEY, AZURE_SEARCH_ENDPOINT and SECRET_OPENAI_API_KEY are set." + ); + {{/useOpenAI}} + {{#useAzureOpenAI}} + throw new Error( + "Missing environment variables - please check that SECRET_AZURE_SEARCH_KEY, AZURE_SEARCH_ENDPOINT, SECRET_AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME are set." + ); + {{/useAzureOpenAI}} + } + + const searchApiKey = process.env.SECRET_AZURE_SEARCH_KEY; + const searchApiEndpoint = process.env.AZURE_SEARCH_ENDPOINT; + const credentials = new AzureKeyCredential(searchApiKey); + + const searchIndexClient = new SearchIndexClient(searchApiEndpoint, credentials); + createIndexIfNotExists(searchIndexClient, index); + // Wait 5 seconds for the index to be created + await delay(5000); + + const searchClient = new SearchClient(searchApiEndpoint, index, credentials); + + const filePath = path.join(__dirname, "./data"); + const files = fs.readdirSync(filePath); + const data = []; + for (let i=1;i<=files.length;i++) { + const content = fs.readFileSync(path.join(filePath, files[i-1]), "utf-8"); + data.push({ + docId: i+"", + docTitle: files[i-1], + description: content, + descriptionVector: await getEmbeddingVector(content), + }); + } + await upsertDocuments(searchClient, data); +} + +main(); + +module.exports = main; diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/utils.js.tpl b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/utils.js.tpl new file mode 100644 index 0000000000..e58d7d4d3e --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/indexers/utils.js.tpl @@ -0,0 +1,118 @@ +/** + * Defines the utility methods. + */ +const { KnownAnalyzerNames } = require("@azure/search-documents"); +const { OpenAIEmbeddings } = require("@microsoft/teams-ai"); +{{#useOpenAI}} +const config = require("../config"); +{{/useOpenAI}} + +/** + * A wrapper for setTimeout that resolves a promise after timeInMs milliseconds. + */ +function delay(timeInMs) { + return new Promise((resolve) => setTimeout(resolve, timeInMs)); +} + +/** + * Deletes the index with the given name + */ +function deleteIndex(client, name) { + return client.deleteIndex(name); +} + +/** + * Adds or updates the given documents in the index + */ +async function upsertDocuments(client, documents) { + return await client.mergeOrUploadDocuments(documents); +} + +/** + * Creates the index with the given name + */ +async function createIndexIfNotExists(client, name) { + const MyDocumentIndex = { + name, + fields: [ + { + type: "Edm.String", + name: "docId", + key: true, + filterable: true, + sortable: true + }, + { + type: "Edm.String", + name: "docTitle", + searchable: true, + filterable: true, + sortable: true + }, + { + type: "Edm.String", + name: "description", + searchable: true, + analyzerName: KnownAnalyzerNames.EnLucene + }, + { + type: "Collection(Edm.Single)", + name: "descriptionVector", + searchable: true, + vectorSearchDimensions: 1536, + vectorSearchProfileName: "my-vector-config" + }, + ], + corsOptions: { + // for browser tests + allowedOrigins: ["*"] + }, + vectorSearch: { + algorithms: [{ name: "vector-search-algorithm", kind: "hnsw" }], + profiles: [ + { + name: "my-vector-config", + algorithmConfigurationName: "vector-search-algorithm" + } + ] + } + }; + + await client.createOrUpdateIndex(MyDocumentIndex); +} + +/** + * Generate the embedding vector + */ +async function getEmbeddingVector(text) { + {{#useOpenAI}} + const embeddings = new OpenAIEmbeddings({ + apiKey: process.env.SECRET_OPENAI_API_KEY, + model: config.openAIEmbeddingModelName + }); + const result = await embeddings.createEmbeddings(config.openAIEmbeddingModelName, text); + {{/useOpenAI}} + {{#useAzureOpenAI}} + const embeddings = new OpenAIEmbeddings({ + azureApiKey: process.env.SECRET_AZURE_OPENAI_API_KEY, + azureEndpoint: process.env.AZURE_OPENAI_ENDPOINT, + azureDeployment: process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME, + }); + + const result = await embeddings.createEmbeddings( process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME, text); + {{/useAzureOpenAI}} + + if (result.status !== "success" || !result.output) { + throw new Error(`Failed to generate embeddings for description: ${text}`); + } + + return result.output[0]; +} + +module.exports = { + deleteIndex, + createIndexIfNotExists, + delay, + upsertDocuments, + getEmbeddingVector, +}; \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/prompts/chat/config.json b/templates/js/custom-copilot-rag-azure-ai-search/src/prompts/chat/config.json new file mode 100644 index 0000000000..4367c3fc5c --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/prompts/chat/config.json @@ -0,0 +1,22 @@ +{ + "schema": 1.1, + "description": "Chat with Teams RAG.", + "type": "completion", + "completion": { + "completion_type": "chat", + "include_history": true, + "include_input": true, + "max_input_tokens": 2800, + "max_tokens": 1000, + "temperature": 0.9, + "top_p": 0.0, + "presence_penalty": 0.6, + "frequency_penalty": 0.0, + "stop_sequences": [] + }, + "augmentation": { + "data_sources": { + "azure-ai-search": 1200 + } + } +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/src/prompts/chat/skprompt.txt b/templates/js/custom-copilot-rag-azure-ai-search/src/prompts/chat/skprompt.txt new file mode 100644 index 0000000000..2a2ebee5a3 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/src/prompts/chat/skprompt.txt @@ -0,0 +1,3 @@ +The following is a conversation with an AI assistant, who is an expert on answering questions over the given context. +Responses should be in a short journalistic style with no more than 80 words. +Use the context provided in the `` tags as the source for your answers. \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/teamsapp.local.yml.tpl b/templates/js/custom-copilot-rag-azure-ai-search/teamsapp.local.yml.tpl new file mode 100644 index 0000000000..4abd73f918 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/teamsapp.local.yml.tpl @@ -0,0 +1,93 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create + with: + botId: ${{BOT_ID}} + name: {{appName}} + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install --no-audit + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs + envs: + BOT_ID: ${{BOT_ID}} + BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + {{#useOpenAI}} + OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME}} + {{/useAzureOpenAI}} + AZURE_SEARCH_KEY: ${{SECRET_AZURE_SEARCH_KEY}} + AZURE_SEARCH_ENDPOINT: ${{AZURE_SEARCH_ENDPOINT}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/teamsapp.testtool.yml.tpl b/templates/js/custom-copilot-rag-azure-ai-search/teamsapp.testtool.yml.tpl new file mode 100644 index 0000000000..20c10e6511 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/teamsapp.testtool.yml.tpl @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.1-beta + symlinkDir: ./devTools/teamsapptester + + # Run npm command + - uses: cli/runNpmCommand + with: + args: install --no-audit + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs.testTool + envs: + {{#useOpenAI}} + OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME}} + {{/useAzureOpenAI}} + AZURE_SEARCH_KEY: ${{SECRET_AZURE_SEARCH_KEY}} + AZURE_SEARCH_ENDPOINT: ${{AZURE_SEARCH_ENDPOINT}} + TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-azure-ai-search/teamsapp.yml.tpl b/templates/js/custom-copilot-rag-azure-ai-search/teamsapp.yml.tpl new file mode 100644 index 0000000000..80699e1edb --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/teamsapp.yml.tpl @@ -0,0 +1,145 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Teams Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsapp deploy' is executed +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install + - uses: cli/runNpmCommand + name: build app + with: + args: run build --if-present + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: . + # Ignore file location, leave blank will ignore nothing + ignoreFile: .webappignore + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + +# Triggered when 'teamsapp publish' is executed +publish: + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Publish the app to + # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps) + # for review and approval + - uses: teamsApp/publishAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + publishedAppId: TEAMS_APP_PUBLISHED_APP_ID diff --git a/templates/js/custom-copilot-rag-azure-ai-search/web.config b/templates/js/custom-copilot-rag-azure-ai-search/web.config new file mode 100644 index 0000000000..0c09f2f869 --- /dev/null +++ b/templates/js/custom-copilot-rag-azure-ai-search/web.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-custom-api/.gitignore b/templates/js/custom-copilot-rag-custom-api/.gitignore index 1cbb2aad8d..07a76d44b3 100644 --- a/templates/js/custom-copilot-rag-custom-api/.gitignore +++ b/templates/js/custom-copilot-rag-custom-api/.gitignore @@ -14,3 +14,6 @@ node_modules/ .env .deployment .DS_Store + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-custom-api/.webappignore b/templates/js/custom-copilot-rag-custom-api/.webappignore index 543734d3ac..a9b39765d9 100644 --- a/templates/js/custom-copilot-rag-custom-api/.webappignore +++ b/templates/js/custom-copilot-rag-custom-api/.webappignore @@ -26,4 +26,5 @@ teamsapp.*.yml /appPackage/build/ /appPackage/*.png /appPackage/manifest.json -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-custom-api/README.md.tpl b/templates/js/custom-copilot-rag-custom-api/README.md.tpl index 6b41e671f0..87344d6e99 100644 --- a/templates/js/custom-copilot-rag-custom-api/README.md.tpl +++ b/templates/js/custom-copilot-rag-custom-api/README.md.tpl @@ -34,7 +34,7 @@ The app template is built using the Teams AI library, which provides the capabil 1. In file *env/.env.testtool.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=`. {{/useOpenAI}} {{#useAzureOpenAI}} -1. In file *env/.env.testtool.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_ENDPOINT=`, endpoint `SECRET_AZURE_OPENAI_ENDPOINT=` and deployment name `AZURE_OPENAI_DEPLOYMENT=`. +1. In file *env/.env.testtool.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_ENDPOINT=`, endpoint `SECRET_AZURE_OPENAI_ENDPOINT=` and deployment name `AZURE_OPENAI_MODEL_DEPLOYMENT_NAME=`. {{/useAzureOpenAI}} 1. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. Select `Debug in Test Tool (Preview)`. 1. You can send any message to get a response from the bot. @@ -49,7 +49,7 @@ The app template is built using the Teams AI library, which provides the capabil 1. In file *env/.env.local.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=`. {{/useOpenAI}} {{#useAzureOpenAI}} -1. In file *env/.env.local.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_ENDPOINT=`, endpoint `SECRET_AZURE_OPENAI_ENDPOINT= and deployment name `AZURE_OPENAI_DEPLOYMENT=`. +1. In file *env/.env.local.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_ENDPOINT=`, endpoint `SECRET_AZURE_OPENAI_ENDPOINT= and deployment name `AZURE_OPENAI_MODEL_DEPLOYMENT_NAME=`. {{/useAzureOpenAI}} 1. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. 1. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams. diff --git a/templates/js/custom-copilot-rag-custom-api/env/.env.dev.user.tpl b/templates/js/custom-copilot-rag-custom-api/env/.env.dev.user.tpl index f0f2496a45..0cf7fab130 100644 --- a/templates/js/custom-copilot-rag-custom-api/env/.env.dev.user.tpl +++ b/templates/js/custom-copilot-rag-custom-api/env/.env.dev.user.tpl @@ -17,11 +17,16 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY=' ' {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT=' ' {{/azureOpenAIEndpoint}} -AZURE_OPENAI_DEPLOYMENT=' ' {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-custom-api/env/.env.local.user.tpl b/templates/js/custom-copilot-rag-custom-api/env/.env.local.user.tpl index af91fac080..7b66fcda20 100644 --- a/templates/js/custom-copilot-rag-custom-api/env/.env.local.user.tpl +++ b/templates/js/custom-copilot-rag-custom-api/env/.env.local.user.tpl @@ -18,11 +18,16 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY=' ' {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT=' ' {{/azureOpenAIEndpoint}} -AZURE_OPENAI_DEPLOYMENT=' ' {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-custom-api/env/.env.testtool.user.tpl b/templates/js/custom-copilot-rag-custom-api/env/.env.testtool.user.tpl index 8678ceb266..33ed92af0b 100644 --- a/templates/js/custom-copilot-rag-custom-api/env/.env.testtool.user.tpl +++ b/templates/js/custom-copilot-rag-custom-api/env/.env.testtool.user.tpl @@ -17,11 +17,16 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY=' ' {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT=' ' {{/azureOpenAIEndpoint}} -AZURE_OPENAI_DEPLOYMENT=' ' {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-custom-api/infra/azure.parameters.json.tpl b/templates/js/custom-copilot-rag-custom-api/infra/azure.parameters.json.tpl index 81e8292e6f..d8cc443814 100644 --- a/templates/js/custom-copilot-rag-custom-api/infra/azure.parameters.json.tpl +++ b/templates/js/custom-copilot-rag-custom-api/infra/azure.parameters.json.tpl @@ -24,7 +24,7 @@ "value": "${{AZURE_OPENAI_ENDPOINT}}" }, "azureOpenAIDeployment": { - "value": "${{AZURE_OPENAI_DEPLOYMENT}}" + "value": "${{AZURE_OPENAI_MODEL_DEPLOYMENT_NAME}}" }, {{/useAzureOpenAI}} "webAppSKU": { diff --git a/templates/js/custom-copilot-rag-custom-api/teamsapp.local.yml.tpl b/templates/js/custom-copilot-rag-custom-api/teamsapp.local.yml.tpl index 267e41f9e0..b8a9b239cc 100644 --- a/templates/js/custom-copilot-rag-custom-api/teamsapp.local.yml.tpl +++ b/templates/js/custom-copilot-rag-custom-api/teamsapp.local.yml.tpl @@ -86,5 +86,5 @@ deploy: {{#useAzureOpenAI}} AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} - AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_DEPLOYMENT}} + AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_MODEL_DEPLOYMENT_NAME}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-custom-api/teamsapp.testtool.yml.tpl b/templates/js/custom-copilot-rag-custom-api/teamsapp.testtool.yml.tpl index 2c9cc4417c..54a837325e 100644 --- a/templates/js/custom-copilot-rag-custom-api/teamsapp.testtool.yml.tpl +++ b/templates/js/custom-copilot-rag-custom-api/teamsapp.testtool.yml.tpl @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command @@ -27,6 +27,6 @@ deploy: {{#useAzureOpenAI}} AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} - AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_DEPLOYMENT}} + AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_MODEL_DEPLOYMENT_NAME}} {{/useAzureOpenAI}} TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/.gitignore b/templates/js/custom-copilot-rag-customize/.gitignore new file mode 100644 index 0000000000..bc090d9176 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/.gitignore @@ -0,0 +1,22 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +.localConfigs +.localConfigs.testTool +.notification.localstore.json +.notification.testtoolstore.json +appPackage/build + +# dependencies +node_modules/ + +# misc +.env +.deployment +.DS_Store + +# build +lib/ + +# devTools +devTools/ \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/.localConfigs.testTool.tpl b/templates/js/custom-copilot-rag-customize/.localConfigs.testTool.tpl new file mode 100644 index 0000000000..ec23f97943 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/.localConfigs.testTool.tpl @@ -0,0 +1,12 @@ +# A gitignored place holder file for local runtime configurations +BOT_ID= +BOT_PASSWORD= +{{#useOpenAI}} +OPENAI_API_KEY= +{{/useOpenAI}} +{{#useAzureOpenAI}} +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/useAzureOpenAI}} +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json diff --git a/templates/js/custom-copilot-rag-customize/.localConfigs.tpl b/templates/js/custom-copilot-rag-customize/.localConfigs.tpl new file mode 100644 index 0000000000..acd128167d --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/.localConfigs.tpl @@ -0,0 +1,11 @@ +# A gitignored place holder file for local runtime configurations +BOT_ID= +BOT_PASSWORD= +{{#useOpenAI}} +OPENAI_API_KEY= +{{/useOpenAI}} +{{#useAzureOpenAI}} +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/.vscode/extensions.json b/templates/js/custom-copilot-rag-customize/.vscode/extensions.json new file mode 100644 index 0000000000..1b70a39308 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension" + ] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/.vscode/launch.json.tpl b/templates/js/custom-copilot-rag-customize/.vscode/launch.json.tpl new file mode 100644 index 0000000000..9e3b45ee1f --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/.vscode/launch.json.tpl @@ -0,0 +1,122 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Attach to Local Service", + "type": "node", + "request": "attach", + "port": 9239, + "restart": true, + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Teams (Edge)", + "configurations": [ + "Launch App (Edge)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { +{{#enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Chrome)", + "configurations": [ + "Launch App (Chrome)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { +{{#enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} + "order": 2 + }, + "stopAll": true + }, + { + "name": "Debug in Test Tool (Preview)", + "configurations": [ + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App (Test Tool)", + "presentation": { +{{#enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} + "order": 1 + }, + "stopAll": true + } + ] +} diff --git a/templates/js/custom-copilot-rag-customize/.vscode/settings.json b/templates/js/custom-copilot-rag-customize/.vscode/settings.json new file mode 100644 index 0000000000..0d3ba10b02 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/.vscode/tasks.json b/templates/js/custom-copilot-rag-customize/.vscode/tasks.json new file mode 100644 index 0000000000..1c3e241f27 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/.vscode/tasks.json @@ -0,0 +1,204 @@ +// This file is automatically generated by Teams Toolkit. +// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0. +// See https://aka.ms/teamsfx-tasks for details on how to customize each task. +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Teams App (Test Tool)", + "dependsOn": [ + "Validate prerequisites (Test Tool)", + "Deploy (Test Tool)", + "Start application (Test Tool)", + "Start Test Tool", + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites (Test Tool)", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239, // app inspector port for Node.js debugger + 56150, // test tool port + ] + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy (Test Tool)", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "testtool", + } + }, + { + "label": "Start application (Test Tool)", + "type": "shell", + "command": "npm run dev:teamsfx:testtool", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}", + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + }, + { + "label": "Start Test Tool", + "type": "shell", + "command": "npm run dev:teamsfx:launch-testtool", + "isBackground": true, + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin:${env:PATH}" + } + }, + "windows": { + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin;${env:PATH}" + } + } + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Listening on" + } + }, + "presentation": { + "panel": "dedicated", + "reveal": "silent" + } + }, + { + "label": "Start Teams App Locally", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Provision", + "Deploy", + "Start application" + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239 // app inspector port for Node.js debugger + ] + } + }, + { + // Start the local tunnel service to forward public URL to local port and inspect traffic. + // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. + "label": "Start local tunnel", + "type": "teamsfx", + "command": "debug-start-local-tunnel", + "args": { + "type": "dev-tunnel", + "ports": [ + { + "portNumber": 3978, + "protocol": "http", + "access": "public", + "writeToEnvironmentFile": { + "endpoint": "BOT_ENDPOINT", // output tunnel endpoint as BOT_ENDPOINT + "domain": "BOT_DOMAIN" // output tunnel domain as BOT_DOMAIN + } + } + ], + "env": "local" + }, + "isBackground": true, + "problemMatcher": "$teamsfx-local-tunnel-watch" + }, + { + // Create the debug resources. + // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args. + "label": "Provision", + "type": "teamsfx", + "command": "provision", + "args": { + "env": "local" + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "local" + } + }, + { + "label": "Start application", + "type": "shell", + "command": "npm run dev:teamsfx", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + } + ] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/.webappignore b/templates/js/custom-copilot-rag-customize/.webappignore new file mode 100644 index 0000000000..f79d01ac12 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/.webappignore @@ -0,0 +1,28 @@ +.webappignore +.fx +.deployment +.localConfigs.testTool +.localConfigs +.notification.localstore.json +.notification.testtoolstore.json +.vscode +*.js.map +*.ts.map +*.ts +.git* +.tsbuildinfo +CHANGELOG.md +readme.md +local.settings.json +test +tsconfig.json +.DS_Store +teamsapp.yml +teamsapp.*.yml +/env/ +/node_modules/.bin +/node_modules/ts-node +/node_modules/typescript +/appPackage/ +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/README.md.tpl b/templates/js/custom-copilot-rag-customize/README.md.tpl new file mode 100644 index 0000000000..c9ad1b682c --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/README.md.tpl @@ -0,0 +1,86 @@ +# Overview of the Customize RAG Bot template + +This app template is built on top of [Teams AI library](https://aka.ms/teams-ai-library). +It showcases how to build an basic RAG bot in Teams capable of chatting with users but with context provided by customize data source. + +- [Overview of the Customize RAG Bot template](#overview-of-the-customize-rag-bot-template) + - [Get started with the Customize RAG Bot template](#get-started-with-the-customize-rag-bot-template) + - [What's included in the template](#whats-included-in-the-template) + - [Extend the Customize RAG Bot template with more AI capabilities](#extend-the-customize-rag-bot-template-with-more-ai-capabilities) + - [Additional information and references](#additional-information-and-references) + +## Get started with the Customize RAG Bot template + +> **Prerequisites** +> +> To run the AI Search bot template in your local dev machine, you will need: +> +> - [Node.js](https://nodejs.org/), supported versions: 16, 18 +> - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +{{#useOpenAI}} +> - An account with [OpenAI](https://platform.openai.com/). +{{/useOpenAI}} +{{#useAzureOpenAI}} +> - Prepare your own [Azure OpenAI](https://aka.ms/oai/access) resource. +{{/useAzureOpenAI}} + +1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#useOpenAI}} +1. In file *env/.env.testtool.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=`. +{{/useOpenAI}} +{{#useAzureOpenAI}} +1. In file *env/.env.testtool.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_API_KEY=`, endpoint `AZURE_OPENAI_ENDPOINT=` and deployment name `AZURE_OPENAI_DEPLOYMENT_NAME=`. +{{/useAzureOpenAI}} +1. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. Select `Debug in Test Tool (Preview)`. +1. You can send any message to get a response from the bot. + +**Congratulations**! You are running an application that can now interact with users in Teams App Test Tool: + +![AI Search Bot](https://github.com/OfficeDev/TeamsFx/assets/13211513/f56e7602-a5d3-436a-ae01-78546d61717d) + +## What's included in the template + +| Folder | Contents | +| - | - | +| `.vscode` | VSCode files for debugging | +| `appPackage` | Templates for the Teams application manifest | +| `env` | Environment files | +| `infra` | Templates for provisioning Azure resources | +| `src` | The source code for the application | + +The following files can be customized and demonstrate an example implementation to get you started. + +| File | Contents | +| - | - | +|`src/index.js`| Sets up the bot app server.| +|`src/adapter.js`| Sets up the bot adapter.| +|`src/config.js`| Defines the environment variables.| +|`src/prompts/chat/skprompt.txt`| Defines the prompt.| +|`src/prompts/chat/config.json`| Configures the prompt.| +|`src/app/app.js`| Handles business logics for the RAG bot.| +|`src/app/myDataSource.js`| Defines the data source.| +|`src/data/*.md`| Raw text data sources.| + +The following are Teams Toolkit specific project files. You can [visit a complete guide on Github](https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview) to understand how Teams Toolkit works. + +| File | Contents | +| - | - | +|`teamsapp.yml`|This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | +|`teamsapp.local.yml`|This overrides `teamsapp.yml` with actions that enable local execution and debugging.| +|`teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool.| + +## Extend the Customize RAG Bot template with more AI capabilities + +You can follow [Build a Basic AI Chatbot in Teams](https://aka.ms/teamsfx-basic-ai-chatbot) to extend the Basic AI Chatbot template with more AI capabilities, like: +- [Customize prompt](https://aka.ms/teamsfx-basic-ai-chatbot#customize-prompt) +- [Customize user input](https://aka.ms/teamsfx-basic-ai-chatbot#customize-user-input) +- [Customize conversation history](https://aka.ms/teamsfx-basic-ai-chatbot#customize-conversation-history) +- [Customize model type](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-type) +- [Customize model parameters](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-parameters) +- [Handle messages with image](https://aka.ms/teamsfx-basic-ai-chatbot#handle-messages-with-image) + +## Additional information and references +- [Teams AI library](https://aka.ms/teams-ai-library) +- [Teams Toolkit Documentations](https://docs.microsoft.com/microsoftteams/platform/toolkit/teams-toolkit-fundamentals) +- [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +- [Teams Toolkit Samples](https://github.com/OfficeDev/TeamsFx-Samples) \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/appPackage/color.png b/templates/js/custom-copilot-rag-customize/appPackage/color.png new file mode 100644 index 0000000000..2d7e85c9e9 Binary files /dev/null and b/templates/js/custom-copilot-rag-customize/appPackage/color.png differ diff --git a/templates/js/custom-copilot-rag-customize/appPackage/manifest.json.tpl b/templates/js/custom-copilot-rag-customize/appPackage/manifest.json.tpl new file mode 100644 index 0000000000..d7a51bc8fb --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/appPackage/manifest.json.tpl @@ -0,0 +1,46 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", + "manifestVersion": "1.16", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.microsoft.teams.extension", + "developer": { + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/privacy", + "termsOfUseUrl": "https://www.example.com/termofuse" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "{{appName}}${{APP_NAME_SUFFIX}}", + "full": "full name for {{appName}}" + }, + "description": { + "short": "short description for {{appName}}", + "full": "full description for {{appName}}" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "${{BOT_ID}}", + "scopes": [ + "personal", + "team", + "groupchat" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "composeExtensions": [], + "configurableTabs": [], + "staticTabs": [], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/appPackage/outline.png b/templates/js/custom-copilot-rag-customize/appPackage/outline.png new file mode 100644 index 0000000000..245fa194db Binary files /dev/null and b/templates/js/custom-copilot-rag-customize/appPackage/outline.png differ diff --git a/templates/js/custom-copilot-rag-customize/env/.env.dev b/templates/js/custom-copilot-rag-customize/env/.env.dev new file mode 100644 index 0000000000..4b07861c03 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/env/.env.dev @@ -0,0 +1,16 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/env/.env.dev.user.tpl b/templates/js/custom-copilot-rag-customize/env/.env.dev.user.tpl new file mode 100644 index 0000000000..ed67f2e2ac --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/env/.env.dev.user.tpl @@ -0,0 +1,32 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/env/.env.local b/templates/js/custom-copilot-rag-customize/env/.env.local new file mode 100644 index 0000000000..f3a75f8723 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/env/.env.local @@ -0,0 +1,11 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=local +APP_NAME_SUFFIX=local + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_DOMAIN= +BOT_ENDPOINT= \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/env/.env.local.user.tpl b/templates/js/custom-copilot-rag-customize/env/.env.local.user.tpl new file mode 100644 index 0000000000..b1c0fc39f2 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/env/.env.local.user.tpl @@ -0,0 +1,33 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/env/.env.testtool b/templates/js/custom-copilot-rag-customize/env/.env.testtool new file mode 100644 index 0000000000..43ce12aad3 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/env/.env.testtool @@ -0,0 +1,8 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=testtool + +# Environment variables used by test tool +TEAMSAPPTESTER_PORT=56150 +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json diff --git a/templates/js/custom-copilot-rag-customize/env/.env.testtool.user.tpl b/templates/js/custom-copilot-rag-customize/env/.env.testtool.user.tpl new file mode 100644 index 0000000000..8beb393d16 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/env/.env.testtool.user.tpl @@ -0,0 +1,32 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/infra/azure.bicep.tpl b/templates/js/custom-copilot-rag-customize/infra/azure.bicep.tpl new file mode 100644 index 0000000000..9a33563951 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/infra/azure.bicep.tpl @@ -0,0 +1,117 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@description('Required when create Azure Bot service') +param botAadAppClientId string + +@secure() +@description('Required by Bot Framework package in your bot project') +param botAadAppClientSecret string + +{{#useOpenAI}} +@secure() +param openAIKey string +{{/useOpenAI}} +{{#useAzureOpenAI}} +@secure() +param azureOpenAIKey string + +@secure() +param azureOpenAIEndpoint string + +@secure() +param azureOpenAIDeploymentName string +{{/useAzureOpenAI}} + +param webAppSKU string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param location string = resourceGroup().location + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + alwaysOn: true + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' // Run Azure App Service from a package file + } + { + name: 'WEBSITE_NODE_DEFAULT_VERSION' + value: '~18' // Set NodeJS version to 18.x for your site + } + { + name: 'RUNNING_ON_AZURE' + value: '1' + } + { + name: 'BOT_ID' + value: botAadAppClientId + } + { + name: 'BOT_PASSWORD' + value: botAadAppClientSecret + } + {{#useOpenAI}} + { + name: 'OPENAI_API_KEY' + value: openAIKey + } + {{/useOpenAI}} + {{#useAzureOpenAI}} + { + name: 'AZURE_OPENAI_API_KEY' + value: azureOpenAIKey + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: azureOpenAIEndpoint + } + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME' + value: azureOpenAIDeploymentName + } + {{/useAzureOpenAI}} + ] + ftpsState: 'FtpsOnly' + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + botAadAppClientId: botAadAppClientId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/templates/js/custom-copilot-rag-customize/infra/azure.parameters.json.tpl b/templates/js/custom-copilot-rag-customize/infra/azure.parameters.json.tpl new file mode 100644 index 0000000000..22a8b2bdf6 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/infra/azure.parameters.json.tpl @@ -0,0 +1,37 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "botAadAppClientId": { + "value": "${{BOT_ID}}" + }, + "botAadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + {{#useOpenAI}} + "openAIKey": { + "value": "${{SECRET_OPENAI_API_KEY}}" + }, + {{/useOpenAI}} + {{#useAzureOpenAI}} + "azureOpenAIKey": { + "value": "${{SECRET_AZURE_OPENAI_API_KEY}}" + }, + "azureOpenAIEndpoint": { + "value": "${{AZURE_OPENAI_ENDPOINT}}" + }, + "azureOpenAIDeploymentName": { + "value": "${{AZURE_OPENAI_DEPLOYMENT_NAME}}" + }, + {{/useAzureOpenAI}} + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "{{appName}}" + } + } +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/infra/botRegistration/azurebot.bicep b/templates/js/custom-copilot-rag-customize/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..ab67c7a56b --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/infra/botRegistration/azurebot.bicep @@ -0,0 +1,37 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param botAadAppClientId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: botAadAppClientId + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/templates/js/custom-copilot-rag-customize/infra/botRegistration/readme.md b/templates/js/custom-copilot-rag-customize/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/package.json.tpl b/templates/js/custom-copilot-rag-customize/package.json.tpl new file mode 100644 index 0000000000..9bf2fbfd81 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/package.json.tpl @@ -0,0 +1,38 @@ +{ + "name": "{{SafeProjectNameLowerCase}}", + "version": "1.0.0", + "msteams": { + "teamsAppId": null + }, + "description": "Microsoft Teams Toolkit RAG Bot Sample with customize data source and Teams AI Library", + "engines": { + "node": "16 || 18" + }, + "author": "Microsoft", + "license": "MIT", + "main": "./src/index.js", + "scripts": { + "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", + "dev:teamsfx:testtool": "env-cmd --silent -f .localConfigs.testTool npm run dev", + "dev:teamsfx:launch-testtool": "env-cmd --silent -f env/.env.testtool teamsapptester start", + "dev": "nodemon --inspect=9239 --signal SIGINT ./src/index.js", + "start": "node ./src/index.js", + "test": "echo \"Error: no test specified\" && exit 1", + "watch": "nodemon --exec \"npm run start\"" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "dependencies": { + "@azure/search-documents": "^12.0.0", + "@microsoft/teams-ai": "^1.1.0", + "botbuilder": "^4.20.0", + "openai": "~4.28.4", + "restify": "^10.0.0" + }, + "devDependencies": { + "env-cmd": "^10.1.0", + "nodemon": "^2.0.7" + } +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/src/adapter.js b/templates/js/custom-copilot-rag-customize/src/adapter.js new file mode 100644 index 0000000000..17ad23563e --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/src/adapter.js @@ -0,0 +1,51 @@ +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +const { + CloudAdapter, + ConfigurationBotFrameworkAuthentication, + ConfigurationServiceClientCredentialFactory, +} = require("botbuilder"); + +// This bot's main dialog. +const config = require("./config"); + +const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication( + {}, + new ConfigurationServiceClientCredentialFactory({ + MicrosoftAppId: config.botId, + MicrosoftAppPassword: process.env.BOT_PASSWORD, + MicrosoftAppType: "MultiTenant", + }) +); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about how bots work. +const adapter = new CloudAdapter(botFrameworkAuthentication); + +// Catch-all for errors. +const onTurnErrorHandler = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. + console.error(`\n [onTurnError] unhandled error: ${error}`); + + // Only send error message for user messages, not for other message types so the bot doesn't spam a channel or chat. + if (context.activity.type === "message") { + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendTraceActivity( + "OnTurnError Trace", + `${error}`, + "https://www.botframework.com/schemas/error", + "TurnError" + ); + + // Send a message to the user + await context.sendActivity("The bot encountered an error or bug."); + await context.sendActivity("To continue to run this bot, please fix the bot source code."); + } +}; + +// Set the onTurnError for the singleton CloudAdapter. +adapter.onTurnError = onTurnErrorHandler; + +module.exports = adapter; diff --git a/templates/js/custom-copilot-rag-customize/src/app/app.js.tpl b/templates/js/custom-copilot-rag-customize/src/app/app.js.tpl new file mode 100644 index 0000000000..542a221fb1 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/src/app/app.js.tpl @@ -0,0 +1,47 @@ +const { MemoryStorage } = require("botbuilder"); +const path = require("path"); +const config = require("../config"); + +// See https://aka.ms/teams-ai-library to learn more about the Teams AI library. +const { Application, ActionPlanner, OpenAIModel, PromptManager } = require("@microsoft/teams-ai"); +const { MyDataSource } = require("./myDataSource"); + +// Create AI components +const model = new OpenAIModel({ + {{#useOpenAI}} + apiKey: config.openAIKey, + defaultModel: config.openAIModelName, + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureApiKey: config.azureOpenAIKey, + azureDefaultDeployment: config.azureOpenAIDeploymentName, + azureEndpoint: config.azureOpenAIEndpoint, + {{/useAzureOpenAI}} + + useSystemMessages: true, + logRequests: true, +}); +const prompts = new PromptManager({ + promptsFolder: path.join(__dirname, "../prompts"), +}); +const planner = new ActionPlanner({ + model, + prompts, + defaultPrompt: "chat", +}); + +// Register your data source with planner +const myDataSource = new MyDataSource("my-ai-search"); +myDataSource.init(); +planner.prompts.addDataSource(myDataSource); + +// Define storage and application +const storage = new MemoryStorage(); +const app = new Application({ + storage, + ai: { + planner, + }, +}); + +module.exports = app; diff --git a/templates/js/custom-copilot-rag-customize/src/app/myDataSource.js.tpl b/templates/js/custom-copilot-rag-customize/src/app/myDataSource.js.tpl new file mode 100644 index 0000000000..5a3ebbeecd --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/src/app/myDataSource.js.tpl @@ -0,0 +1,59 @@ +const path = require("path"); +const fs = require("fs"); + +/** + * A data source that searches through a local directory of files for a given query. + */ +class MyDataSource { + /** + * Creates a new instance of the MyDataSource instance. + */ + constructor(name) { + this.name = name; + } + + /** + * Initializes the data source. + */ + init() { + const filePath = path.join(__dirname, "../data"); + const files = fs.readdirSync(filePath); + this._data = files.map(file => { + return fs.readFileSync(path.join(filePath, file), "utf-8"); + }); + } + + /** + * Renders the data source as a string of text. + */ + async renderData(context, memory, tokenizer, maxTokens) { + const query = memory.getValue("temp.input"); + if(!query) { + return { output: "", length: 0, tooLong: false }; + } + for (let data of this._data) { + if (data.includes(query)) { + return { output: this.formatDocument(data), length: data.length, tooLong: false }; + } + } + if (query.toLocaleLowerCase().includes("perksplus")) { + return { output: this.formatDocument(this._data[0]), length: this._data[0].length, tooLong: false }; + } else if (query.toLocaleLowerCase().includes("company") || query.toLocaleLowerCase().includes("history")) { + return { output: this.formatDocument(this._data[1]), length: this._data[1].length, tooLong: false }; + } else if (query.toLocaleLowerCase().includes("northwind") || query.toLocaleLowerCase().includes("health")) { + return { output: this.formatDocument(this._data[2]), length: this._data[2].length, tooLong: false }; + } + return { output: "", length: 0, tooLong: false }; + } + + /** + * Formats the result string + */ + formatDocument(result) { + return `${result}`; + } +} + +module.exports = { + MyDataSource, +}; \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/src/config.js.tpl b/templates/js/custom-copilot-rag-customize/src/config.js.tpl new file mode 100644 index 0000000000..33035ba037 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/src/config.js.tpl @@ -0,0 +1,15 @@ +const config = { + botId: process.env.BOT_ID, + botPassword: process.env.BOT_PASSWORD, + {{#useOpenAI}} + openAIKey: process.env.OPENAI_API_KEY, + openAIModelName: "gpt-3.5-turbo", + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureOpenAIKey: process.env.AZURE_OPENAI_API_KEY, + azureOpenAIEndpoint: process.env.AZURE_OPENAI_ENDPOINT, + azureOpenAIDeploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME, + {{/useAzureOpenAI}} +}; + +module.exports = config; diff --git a/templates/js/custom-copilot-rag-customize/src/data/Contoso Electronics_PerkPlus_Program.md b/templates/js/custom-copilot-rag-customize/src/data/Contoso Electronics_PerkPlus_Program.md new file mode 100644 index 0000000000..1d97d5117e --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/src/data/Contoso Electronics_PerkPlus_Program.md @@ -0,0 +1,36 @@ +# Contoso Electronics PerksPlus Program + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Overview +Introducing PerksPlus - the ultimate benefits program designed to support the health and wellness of employees. With PerksPlus, employees have the opportunity to expense up to $1000 for fitness-related programs, making it easier and more affordable to maintain a healthy lifestyle. PerksPlus is not only designed to support employees' physical health, but also their mental health. Regular exercise has been shown to reduce stress, improve mood, and enhance overall well-being. With PerksPlus, employees can invest in their health and wellness, while enjoying the peace of mind that comes with knowing they are getting the support they need to lead a healthy life. +What is Covered? + +PerksPlus covers a wide range of fitness activities, including but not limited to: +* Gym memberships +* Personal training sessions +* Yoga and Pilates classes +* Fitness equipment purchases +* Sports team fees +* Health retreats and spas +* Outdoor adventure activities (such as rock climbing, hiking, and kayaking) +* Group fitness classes (such as dance, martial arts, and cycling) +* Virtual fitness programs (such as online yoga and workout classes) + +In addition to the wide range of fitness activities covered by PerksPlus, the program also covers a variety of lessons and experiences that promote health and wellness. Some of the lessons covered under PerksPlus include: +* Skiing and snowboarding lessons +* Scuba diving lessons +* Surfing lessons +* Horseback riding lessons + +These lessons provide employees with the opportunity to try new things, challenge themselves, and improve their physical skills. They are also a great way to relieve stress and have fun while staying active. + +With PerksPlus, employees can choose from a variety of fitness programs to suit their individual needs and preferences. Whether you're looking to improve your physical fitness, reduce stress, or just have some fun, PerksPlus has you covered. + +## What is Not Covered? +In addition to the wide range of activities covered by PerksPlus, there is also a list of things that are not +covered under the program. These include but are not limited to: +* Non-fitness related expenses +* Medical treatments and procedures +* Travel expenses (unless related to a fitness program) +* Food and supplements \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/src/data/Contoso_Electronics_Company_Overview.md b/templates/js/custom-copilot-rag-customize/src/data/Contoso_Electronics_Company_Overview.md new file mode 100644 index 0000000000..6878a8e204 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/src/data/Contoso_Electronics_Company_Overview.md @@ -0,0 +1,48 @@ +# Contoso Electronics Company Overview + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## History + +Contoso Electronics, a pioneering force in the tech industry, was founded in 1985 by visionary entrepreneurs with a passion for innovation. Over the years, the company has played a pivotal role in shaping the landscape of consumer electronics. + +| Year | Milestone | +|------|-----------| +| 1985 | Company founded with a focus on cutting-edge technology | +| 1990 | Launched the first-ever handheld personal computer | +| 2000 | Introduced groundbreaking advancements in AI and robotics | +| 2015 | Expansion into sustainable and eco-friendly product lines | + +## Company Overview + +At Contoso Electronics, we take pride in fostering a dynamic and inclusive workplace. Our dedicated team of experts collaborates to create innovative solutions that empower and connect people globally. + +### Core Values + +- **Innovation:** Constantly pushing the boundaries of technology. +- **Diversity:** Embracing different perspectives for creative excellence. +- **Sustainability:** Committed to eco-friendly practices in our products. + +## Vacation Perks + +We believe in work-life balance and understand the importance of well-deserved breaks. Our vacation perks are designed to help our employees recharge and return with renewed enthusiasm. + +| Vacation Tier | Duration | Additional Benefits | +|---------------|----------|---------------------| +| Standard | 2 weeks | Health and wellness stipend | +| Senior | 4 weeks | Travel vouchers for a dream destination | +| Executive | 6 weeks | Luxury resort getaway with family | + +## Employee Recognition + +Recognizing the hard work and dedication of our employees is at the core of our culture. Here are some ways we celebrate achievements: + +- Monthly "Innovator of the Month" awards +- Annual gala with awards for outstanding contributions +- Team-building retreats for high-performing departments + +## Join Us! + +Contoso Electronics is always on the lookout for talented individuals who share our passion for innovation. If you're ready to be part of a dynamic team shaping the future of technology, check out our [careers page](http://www.contoso.com) for exciting opportunities. + +[Learn more about Contoso Electronics!](http://www.contoso.com) diff --git a/templates/js/custom-copilot-rag-customize/src/data/Contoso_Electronics_Plan_Benefits.md b/templates/js/custom-copilot-rag-customize/src/data/Contoso_Electronics_Plan_Benefits.md new file mode 100644 index 0000000000..9da5c6429d --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/src/data/Contoso_Electronics_Plan_Benefits.md @@ -0,0 +1,37 @@ +# Contoso Electronics Plan and Benefit Packages + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Northwind Health Plus + +Northwind Health Plus is a comprehensive plan that provides comprehensive coverage for medical, vision, and dental services. This plan also offers prescription drug coverage, mental health and substance abuse coverage, and coverage for preventive care services. With Northwind Health Plus, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. + +This plan also offers coverage for emergency services, both in-network and out-of-network. + +## Northwind Standard + +Northwind Standard is a basic plan that provides coverage for medical, vision, and dental services. This plan also offers coverage for preventive care services, as well as prescription drug coverage. With Northwind Standard, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. This plan does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +## Comparison of Plans + +Both plans offer coverage for routine physicals, well-child visits, immunizations, and other preventive care services. The plans also cover preventive care services such as mammograms, colonoscopies, and other cancer screenings. + +Northwind Health Plus offers more comprehensive coverage than Northwind Standard. This plan offers coverage for emergency services, both in-network and out-of-network, as well as mental health and substance abuse coverage. Northwind Standard does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +Both plans offer coverage for prescription drugs. Northwind Health Plus offers a wider range of prescription drug coverage than Northwind Standard. Northwind Health Plus covers generic, brand-name, and specialty drugs, while Northwind Standard only covers generic and brand-name drugs. + +Both plans offer coverage for vision and dental services. Northwind Health Plus offers coverage for vision exams, glasses, and contact lenses, as well as dental exams, cleanings, and fillings. Northwind Standard only offers coverage for vision exams and glasses. + +Both plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays, doctor visits, lab tests, and X-rays. Northwind Standard only offers coverage for doctor visits and lab tests. + +Northwind Health Plus is a comprehensive plan that offers more coverage than Northwind Standard. Northwind Health Plus offers coverage for emergency services, mental health and substance abuse coverage, and out-of-network services, while Northwind Standard does not. Northwind Health Plus also offers a wider range of prescription drug coverage than Northwind Standard. Both plans offer coverage for vision and dental services, as well as medical services. + +## Cost Comparison + +Contoso Electronics deducts the employee's portion of the healthcare cost from each paycheck. This means that the cost of the health insurance will be spread out over the course of the year, rather than being paid in one lump sum. The employee's portion of the cost will be calculated based on the selected health plan and the number of people covered by the insurance. The table below shows a cost comparison between the different health plans offered by Contoso Electronics + +| | Northwind Standard | NorthWind Health Plus | +|---------------|----------|---------------------| +| Employee Only | $45.00 | $55.00 | +| Employee +1 | $65.00 | $71.00 | +| Employee +2 or more | $78.00 | $89.00 | \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/src/index.js b/templates/js/custom-copilot-rag-customize/src/index.js new file mode 100644 index 0000000000..d1e686f00d --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/src/index.js @@ -0,0 +1,25 @@ +// Import required packages +const restify = require("restify"); + +// This bot's adapter +const adapter = require("./adapter"); + +// This bot's main dialog. +const app = require("./app/app"); + +// Create HTTP server. +const server = restify.createServer(); +server.use(restify.plugins.bodyParser()); + +server.listen(process.env.port || process.env.PORT || 3978, () => { + console.log(`\nBot Started, ${server.name} listening to ${server.url}`); +}); + +// Listen for incoming server requests. +server.post("/api/messages", async (req, res) => { + // Route received a request to adapter for processing + await adapter.process(req, res, async (context) => { + // Dispatch to application for routing + await app.run(context); + }); +}); diff --git a/templates/js/custom-copilot-rag-customize/src/prompts/chat/config.json b/templates/js/custom-copilot-rag-customize/src/prompts/chat/config.json new file mode 100644 index 0000000000..8bd96c0062 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/src/prompts/chat/config.json @@ -0,0 +1,22 @@ +{ + "schema": 1.1, + "description": "Chat with Teams RAG.", + "type": "completion", + "completion": { + "completion_type": "chat", + "include_history": true, + "include_input": true, + "max_input_tokens": 2800, + "max_tokens": 1000, + "temperature": 0.9, + "top_p": 0.0, + "presence_penalty": 0.6, + "frequency_penalty": 0.0, + "stop_sequences": [] + }, + "augmentation": { + "data_sources": { + "my-ai-search": 1200 + } + } +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/src/prompts/chat/skprompt.txt b/templates/js/custom-copilot-rag-customize/src/prompts/chat/skprompt.txt new file mode 100644 index 0000000000..2a2ebee5a3 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/src/prompts/chat/skprompt.txt @@ -0,0 +1,3 @@ +The following is a conversation with an AI assistant, who is an expert on answering questions over the given context. +Responses should be in a short journalistic style with no more than 80 words. +Use the context provided in the `` tags as the source for your answers. \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/teamsapp.local.yml.tpl b/templates/js/custom-copilot-rag-customize/teamsapp.local.yml.tpl new file mode 100644 index 0000000000..55316677e1 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/teamsapp.local.yml.tpl @@ -0,0 +1,90 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create + with: + botId: ${{BOT_ID}} + name: {{appName}} + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install --no-audit + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs + envs: + BOT_ID: ${{BOT_ID}} + BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + {{#useOpenAI}} + OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/teamsapp.testtool.yml.tpl b/templates/js/custom-copilot-rag-customize/teamsapp.testtool.yml.tpl new file mode 100644 index 0000000000..52954e85ca --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/teamsapp.testtool.yml.tpl @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.1-beta + symlinkDir: ./devTools/teamsapptester + + # Run npm command + - uses: cli/runNpmCommand + with: + args: install --no-audit + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs.testTool + envs: + {{#useOpenAI}} + OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + {{/useAzureOpenAI}} + TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-customize/teamsapp.yml.tpl b/templates/js/custom-copilot-rag-customize/teamsapp.yml.tpl new file mode 100644 index 0000000000..80699e1edb --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/teamsapp.yml.tpl @@ -0,0 +1,145 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Teams Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsapp deploy' is executed +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install + - uses: cli/runNpmCommand + name: build app + with: + args: run build --if-present + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: . + # Ignore file location, leave blank will ignore nothing + ignoreFile: .webappignore + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + +# Triggered when 'teamsapp publish' is executed +publish: + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Publish the app to + # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps) + # for review and approval + - uses: teamsApp/publishAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + publishedAppId: TEAMS_APP_PUBLISHED_APP_ID diff --git a/templates/js/custom-copilot-rag-customize/web.config b/templates/js/custom-copilot-rag-customize/web.config new file mode 100644 index 0000000000..0c09f2f869 --- /dev/null +++ b/templates/js/custom-copilot-rag-customize/web.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/.gitignore b/templates/js/custom-copilot-rag-microsoft365/.gitignore new file mode 100644 index 0000000000..bc090d9176 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/.gitignore @@ -0,0 +1,22 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +.localConfigs +.localConfigs.testTool +.notification.localstore.json +.notification.testtoolstore.json +appPackage/build + +# dependencies +node_modules/ + +# misc +.env +.deployment +.DS_Store + +# build +lib/ + +# devTools +devTools/ \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/.localConfigs.tpl b/templates/js/custom-copilot-rag-microsoft365/.localConfigs.tpl new file mode 100644 index 0000000000..acd128167d --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/.localConfigs.tpl @@ -0,0 +1,11 @@ +# A gitignored place holder file for local runtime configurations +BOT_ID= +BOT_PASSWORD= +{{#useOpenAI}} +OPENAI_API_KEY= +{{/useOpenAI}} +{{#useAzureOpenAI}} +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/.vscode/extensions.json b/templates/js/custom-copilot-rag-microsoft365/.vscode/extensions.json new file mode 100644 index 0000000000..1b70a39308 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension" + ] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/.vscode/launch.json.tpl b/templates/js/custom-copilot-rag-microsoft365/.vscode/launch.json.tpl new file mode 100644 index 0000000000..b2248e589a --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/.vscode/launch.json.tpl @@ -0,0 +1,95 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Attach to Local Service", + "type": "node", + "request": "attach", + "port": 9239, + "restart": true, + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Teams (Edge)", + "configurations": [ + "Launch App (Edge)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { + "group": "1-local", + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Chrome)", + "configurations": [ + "Launch App (Chrome)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { + "group": "1-local", + "order": 2 + }, + "stopAll": true + } + ] +} diff --git a/templates/js/custom-copilot-rag-microsoft365/.vscode/settings.json b/templates/js/custom-copilot-rag-microsoft365/.vscode/settings.json new file mode 100644 index 0000000000..0d3ba10b02 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/.vscode/tasks.json b/templates/js/custom-copilot-rag-microsoft365/.vscode/tasks.json new file mode 100644 index 0000000000..585f86ae9a --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/.vscode/tasks.json @@ -0,0 +1,105 @@ +// This file is automatically generated by Teams Toolkit. +// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0. +// See https://aka.ms/teamsfx-tasks for details on how to customize each task. +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Teams App Locally", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Provision", + "Deploy", + "Start application" + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239 // app inspector port for Node.js debugger + ] + } + }, + { + // Start the local tunnel service to forward public URL to local port and inspect traffic. + // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. + "label": "Start local tunnel", + "type": "teamsfx", + "command": "debug-start-local-tunnel", + "args": { + "type": "dev-tunnel", + "ports": [ + { + "portNumber": 3978, + "protocol": "http", + "access": "public", + "writeToEnvironmentFile": { + "endpoint": "BOT_ENDPOINT", // output tunnel endpoint as BOT_ENDPOINT + "domain": "BOT_DOMAIN" // output tunnel domain as BOT_DOMAIN + } + } + ], + "env": "local" + }, + "isBackground": true, + "problemMatcher": "$teamsfx-local-tunnel-watch" + }, + { + // Create the debug resources. + // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args. + "label": "Provision", + "type": "teamsfx", + "command": "provision", + "args": { + "env": "local" + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "local" + } + }, + { + "label": "Start application", + "type": "shell", + "command": "npm run dev:teamsfx", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + } + ] +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/.webappignore b/templates/js/custom-copilot-rag-microsoft365/.webappignore new file mode 100644 index 0000000000..18a015a2a3 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/.webappignore @@ -0,0 +1,27 @@ +.webappignore +.fx +.deployment +.localConfigs.testTool +.localConfigs +.notification.localstore.json +.notification.testtoolstore.json +.vscode +*.js.map +*.ts.map +*.ts +.git* +.tsbuildinfo +CHANGELOG.md +readme.md +local.settings.json +test +tsconfig.json +.DS_Store +teamsapp.yml +teamsapp.*.yml +/env/ +/node_modules/.bin +/node_modules/ts-node +/node_modules/typescript +/appPackage/ +/infra/ \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/README.md.tpl b/templates/js/custom-copilot-rag-microsoft365/README.md.tpl new file mode 100644 index 0000000000..6fddbd45a7 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/README.md.tpl @@ -0,0 +1,90 @@ +# Overview of the M365 RAG Bot template + +This app template is built on top of [Teams AI library](https://aka.ms/teams-ai-library). +It showcases how to build an RAG bot in Teams capable of chatting with users but with context provided by M365 content from Microsoft Graph Search API. + +- [Overview of the M365 RAG Bot template](#overview-of-the-m365-rag-bot-template) + - [Get started with the M365 RAG bot template](#get-started-with-the-m365-rag-bot-template) + - [What's included in the template](#whats-included-in-the-template) + - [Extend the M365 RAG Bot template with more AI capabilities](#extend-the-m365-rag-bot-template-with-more-ai-capabilities) + - [Additional information and references](#additional-information-and-references) + +## Get started with the M365 RAG Bot template + +> **Prerequisites** +> +> To run the AI Search bot template in your local dev machine, you will need: +> +> - [Node.js](https://nodejs.org/), supported versions: 16, 18 +> - A Microsoft 365 tenant in which you have permission to upload Teams apps. You can get a free Microsoft 365 developer tenant by joining the [Microsoft 365 developer program](https://developer.microsoft.com/en-us/microsoft-365/dev-program). +> - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli). +{{#useOpenAI}} +> - An account with [OpenAI](https://platform.openai.com/). +{{/useOpenAI}} +{{#useAzureOpenAI}} +> - Prepare your own [Azure OpenAI](https://aka.ms/oai/access) resource. +{{/useAzureOpenAI}} + +1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#useOpenAI}} +1. In file *env/.env.local.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=`. +{{/useOpenAI}} +{{#useAzureOpenAI}} +1. In file *env/.env.local.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_API_KEY=`, endpoint `AZURE_OPENAI_ENDPOINT=` and deployment name `AZURE_OPENAI_DEPLOYMENT_NAME=`. +{{/useAzureOpenAI}} +1. Microsoft Graph Search API is available for searching SharePoint content, thus you just need to ensure your document in *src/data/\*.md* is uploaded to SharePoint / OneDrive, no extra data ingestion required. +1. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. +1. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams. +1. You can send any message to get a response from the bot. + +**Congratulations**! You are running an application that can now interact with users in Teams App Test Tool: + +![M365 RAG Bot](https://github.com/OfficeDev/TeamsFx/assets/13211513/c2fff68c-53ce-445a-a101-97f0c127b825) + +## What's included in the template + +| Folder | Contents | +| - | - | +| `.vscode` | VSCode files for debugging | +| `appPackage` | Templates for the Teams application manifest | +| `env` | Environment files | +| `infra` | Templates for provisioning Azure resources | +| `src` | The source code for the application | + +The following files can be customized and demonstrate an example implementation to get you started. + +| File | Contents | +| - | - | +|`src/index.js`| Sets up the bot app server.| +|`src/adapter.js`| Sets up the bot adapter.| +|`src/config.js`| Defines the environment variables.| +|`src/prompts/chat/skprompt.txt`| Defines the prompt.| +|`src/prompts/chat/config.json`| Configures the prompt.| +|`src/app/app.js`| Handles business logics for the RAG bot.| +|`src/app/m365DataSource.js`| Defines the m365 data source.| +|`src/data/*.md`| Raw text data sources.| +|`src/public/*.html`| Auth start page and an auth end page to be used by the user sign in flow.| + +The following are Teams Toolkit specific project files. You can [visit a complete guide on Github](https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview) to understand how Teams Toolkit works. + +| File | Contents | +| - | - | +|`teamsapp.yml`|This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | +|`teamsapp.local.yml`|This overrides `teamsapp.yml` with actions that enable local execution and debugging.| +|`teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool.| + +## Extend the M365 RAG Bot template with more AI capabilities + +You can follow [Build a Basic AI Chatbot in Teams](https://aka.ms/teamsfx-basic-ai-chatbot) to extend the Basic AI Chatbot template with more AI capabilities, like: +- [Customize prompt](https://aka.ms/teamsfx-basic-ai-chatbot#customize-prompt) +- [Customize user input](https://aka.ms/teamsfx-basic-ai-chatbot#customize-user-input) +- [Customize conversation history](https://aka.ms/teamsfx-basic-ai-chatbot#customize-conversation-history) +- [Customize model type](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-type) +- [Customize model parameters](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-parameters) +- [Handle messages with image](https://aka.ms/teamsfx-basic-ai-chatbot#handle-messages-with-image) + +## Additional information and references +- [Teams AI library](https://aka.ms/teams-ai-library) +- [Teams Toolkit Documentations](https://docs.microsoft.com/microsoftteams/platform/toolkit/teams-toolkit-fundamentals) +- [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +- [Teams Toolkit Samples](https://github.com/OfficeDev/TeamsFx-Samples) \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/aad.manifest.json.tpl b/templates/js/custom-copilot-rag-microsoft365/aad.manifest.json.tpl new file mode 100644 index 0000000000..1ba5cad9c0 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/aad.manifest.json.tpl @@ -0,0 +1,101 @@ +{ + "id": "${{AAD_APP_OBJECT_ID}}", + "appId": "${{AAD_APP_CLIENT_ID}}", + "name": "{{appName}}-aad", + "accessTokenAcceptedVersion": 2, + "signInAudience": "AzureADMyOrg", + "optionalClaims": { + "idToken": [], + "accessToken": [ + { + "name": "idtyp", + "source": null, + "essential": false, + "additionalProperties": [] + } + ], + "saml2Token": [] + }, + "requiredResourceAccess": [ + { + "resourceAppId": "Microsoft Graph", + "resourceAccess": [ + { + "id": "Files.Read.All", + "type": "Scope" + } + ] + } + ], + "oauth2Permissions": [ + { + "adminConsentDescription": "Allows Teams to call the app's web APIs as the current user.", + "adminConsentDisplayName": "Teams can access app's web APIs", + "id": "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}", + "isEnabled": true, + "type": "User", + "userConsentDescription": "Enable Teams to call this app's web APIs with the same rights that you have", + "userConsentDisplayName": "Teams can access app's web APIs and make requests on your behalf", + "value": "access_as_user" + } + ], + "preAuthorizedApplications": [ + { + "appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "d3590ed6-52b3-4102-aeff-aad2292ab01c", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "00000002-0000-0ff1-ce00-000000000000", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "bc59ab01-8403-45c6-8796-ac3ef710b3e3", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "0ec893e0-5785-4de6-99da-4ed124e5296c", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "4765445b-32c6-49b0-83e6-1d93765276ca", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "4345a7b9-9a63-4910-a426-35363201d503", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + } + ], + "identifierUris":[ + "api://botid-${{BOT_ID}}" + ], + "replyUrlsWithType":[ + { + "url": "https://${{BOT_DOMAIN}}/auth-end.html", + "type": "Web" + } + ] +} diff --git a/templates/js/custom-copilot-rag-microsoft365/appPackage/color.png b/templates/js/custom-copilot-rag-microsoft365/appPackage/color.png new file mode 100644 index 0000000000..2d7e85c9e9 Binary files /dev/null and b/templates/js/custom-copilot-rag-microsoft365/appPackage/color.png differ diff --git a/templates/js/custom-copilot-rag-microsoft365/appPackage/manifest.json.tpl b/templates/js/custom-copilot-rag-microsoft365/appPackage/manifest.json.tpl new file mode 100644 index 0000000000..34072a4676 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/appPackage/manifest.json.tpl @@ -0,0 +1,52 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", + "manifestVersion": "1.16", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.microsoft.teams.extension", + "developer": { + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/privacy", + "termsOfUseUrl": "https://www.example.com/termofuse" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "{{appName}}${{APP_NAME_SUFFIX}}", + "full": "full name for {{appName}}" + }, + "description": { + "short": "short description for {{appName}}", + "full": "full description for {{appName}}" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "${{BOT_ID}}", + "scopes": [ + "personal", + "team", + "groupchat" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "composeExtensions": [], + "configurableTabs": [], + "staticTabs": [], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [ + "${{BOT_DOMAIN}}" + ], + "webApplicationInfo": { + "id": "${{AAD_APP_CLIENT_ID}}", + "resource": "api://botid-${{BOT_ID}}" + } +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/appPackage/outline.png b/templates/js/custom-copilot-rag-microsoft365/appPackage/outline.png new file mode 100644 index 0000000000..245fa194db Binary files /dev/null and b/templates/js/custom-copilot-rag-microsoft365/appPackage/outline.png differ diff --git a/templates/js/custom-copilot-rag-microsoft365/env/.env.dev b/templates/js/custom-copilot-rag-microsoft365/env/.env.dev new file mode 100644 index 0000000000..8cc91bf0c2 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/env/.env.dev @@ -0,0 +1,22 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= +AAD_APP_CLIENT_ID= +AAD_APP_OBJECT_ID= +AAD_APP_TENANT_ID= +AAD_APP_OAUTH_AUTHORITY= +AAD_APP_OAUTH_AUTHORITY_HOST= +AAD_APP_ACCESS_AS_USER_PERMISSION_ID= \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/env/.env.dev.user.tpl b/templates/js/custom-copilot-rag-microsoft365/env/.env.dev.user.tpl new file mode 100644 index 0000000000..a077330011 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/env/.env.dev.user.tpl @@ -0,0 +1,33 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +SECRET_AAD_APP_CLIENT_SECRET= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/env/.env.local b/templates/js/custom-copilot-rag-microsoft365/env/.env.local new file mode 100644 index 0000000000..5574fde921 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/env/.env.local @@ -0,0 +1,17 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=local +APP_NAME_SUFFIX=local + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_DOMAIN= +BOT_ENDPOINT= +AAD_APP_CLIENT_ID= +AAD_APP_OBJECT_ID= +AAD_APP_TENANT_ID= +AAD_APP_OAUTH_AUTHORITY= +AAD_APP_OAUTH_AUTHORITY_HOST= +AAD_APP_ACCESS_AS_USER_PERMISSION_ID= \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/env/.env.local.user.tpl b/templates/js/custom-copilot-rag-microsoft365/env/.env.local.user.tpl new file mode 100644 index 0000000000..f68ff06c67 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/env/.env.local.user.tpl @@ -0,0 +1,34 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +SECRET_AAD_APP_CLIENT_SECRET= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/infra/azure.bicep.tpl b/templates/js/custom-copilot-rag-microsoft365/infra/azure.bicep.tpl new file mode 100644 index 0000000000..9488f43122 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/infra/azure.bicep.tpl @@ -0,0 +1,146 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@description('Required when create Azure Bot service') +param botAadAppClientId string + +@secure() +@description('Required by Bot Framework package in your bot project') +param botAadAppClientSecret string + +{{#useOpenAI}} +@secure() +param openAIKey string +{{/useOpenAI}} +{{#useAzureOpenAI}} +@secure() +param azureOpenAIKey string + +@secure() +param azureOpenAIEndpoint string + +@secure() +param azureOpenAIDeploymentName string +{{/useAzureOpenAI}} + +param webAppSKU string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param location string = resourceGroup().location +param aadAppClientId string +param aadAppTenantId string +param aadAppOauthAuthorityHost string +@secure() +param aadAppClientSecret string + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + alwaysOn: true + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' // Run Azure App Service from a package file + } + { + name: 'WEBSITE_NODE_DEFAULT_VERSION' + value: '~18' // Set NodeJS version to 18.x for your site + } + { + name: 'RUNNING_ON_AZURE' + value: '1' + } + { + name: 'BOT_ID' + value: botAadAppClientId + } + { + name: 'BOT_PASSWORD' + value: botAadAppClientSecret + } + {{#useOpenAI}} + { + name: 'OPENAI_API_KEY' + value: openAIKey + } + {{/useOpenAI}} + {{#useAzureOpenAI}} + { + name: 'AZURE_OPENAI_API_KEY' + value: azureOpenAIKey + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: azureOpenAIEndpoint + } + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME' + value: azureOpenAIDeploymentName + } + {{/useAzureOpenAI}} + ] + ftpsState: 'FtpsOnly' + } + } +} + +resource webAppSettings 'Microsoft.Web/sites/config@2021-02-01' = { + name: '${webAppName}/appsettings' + properties: { + WEBSITE_NODE_DEFAULT_VERSION: '~18' + WEBSITE_RUN_FROM_PACKAGE: '1' + BOT_ID: botAadAppClientId + BOT_PASSWORD: botAadAppClientSecret + BOT_DOMAIN: webApp.properties.defaultHostName + AAD_APP_CLIENT_ID: aadAppClientId + AAD_APP_CLIENT_SECRET: aadAppClientSecret + AAD_APP_TENANT_ID: aadAppTenantId + AAD_APP_OAUTH_AUTHORITY_HOST: aadAppOauthAuthorityHost + {{#useOpenAI}} + OPENAI_API_KEY: openAIKey + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: azureOpenAIKey + AZURE_OPENAI_ENDPOINT: azureOpenAIEndpoint + AZURE_OPENAI_DEPLOYMENT_NAME: azureOpenAIDeploymentName + {{/useAzureOpenAI}} + RUNNING_ON_AZURE: '1' + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + botAadAppClientId: botAadAppClientId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/templates/js/custom-copilot-rag-microsoft365/infra/azure.parameters.json.tpl b/templates/js/custom-copilot-rag-microsoft365/infra/azure.parameters.json.tpl new file mode 100644 index 0000000000..1fbc1e0ea3 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/infra/azure.parameters.json.tpl @@ -0,0 +1,49 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "botAadAppClientId": { + "value": "${{BOT_ID}}" + }, + "botAadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + {{#useOpenAI}} + "openAIKey": { + "value": "${{SECRET_OPENAI_API_KEY}}" + }, + {{/useOpenAI}} + {{#useAzureOpenAI}} + "azureOpenAIKey": { + "value": "${{SECRET_AZURE_OPENAI_API_KEY}}" + }, + "azureOpenAIEndpoint": { + "value": "${{AZURE_OPENAI_ENDPOINT}}" + }, + "azureOpenAIDeploymentName": { + "value": "${{AZURE_OPENAI_DEPLOYMENT_NAME}}" + }, + {{/useAzureOpenAI}} + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "{{appName}}" + }, + "aadAppClientId": { + "value": "${{AAD_APP_CLIENT_ID}}" + }, + "aadAppClientSecret": { + "value": "${{SECRET_AAD_APP_CLIENT_SECRET}}" + }, + "aadAppTenantId": { + "value": "${{AAD_APP_TENANT_ID}}" + }, + "aadAppOauthAuthorityHost": { + "value": "${{AAD_APP_OAUTH_AUTHORITY_HOST}}" + } + } +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/infra/botRegistration/azurebot.bicep b/templates/js/custom-copilot-rag-microsoft365/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..ab67c7a56b --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/infra/botRegistration/azurebot.bicep @@ -0,0 +1,37 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param botAadAppClientId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: botAadAppClientId + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/templates/js/custom-copilot-rag-microsoft365/infra/botRegistration/readme.md b/templates/js/custom-copilot-rag-microsoft365/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/package.json.tpl b/templates/js/custom-copilot-rag-microsoft365/package.json.tpl new file mode 100644 index 0000000000..002cdca69d --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/package.json.tpl @@ -0,0 +1,39 @@ +{ + "name": "{{SafeProjectNameLowerCase}}", + "version": "1.0.0", + "msteams": { + "teamsAppId": null + }, + "description": "Microsoft Teams Toolkit RAG Bot Sample with Graph API and Teams AI Library", + "engines": { + "node": "16 || 18" + }, + "author": "Microsoft", + "license": "MIT", + "main": "./src/index.js", + "scripts": { + "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", + "dev:teamsfx:testtool": "env-cmd --silent -f .localConfigs.testTool npm run dev", + "dev:teamsfx:launch-testtool": "env-cmd --silent -f env/.env.testtool teamsapptester start", + "dev": "nodemon --inspect=9239 --signal SIGINT ./src/index.js", + "start": "node ./src/index.js", + "test": "echo \"Error: no test specified\" && exit 1", + "watch": "nodemon --exec \"npm run start\"" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "dependencies": { + "@microsoft/microsoft-graph-client": "^3.0.1", + "@azure/search-documents": "^12.0.0", + "@microsoft/teams-ai": "^1.1.0", + "botbuilder": "^4.20.0", + "openai": "~4.28.4", + "restify": "^10.0.0" + }, + "devDependencies": { + "env-cmd": "^10.1.0", + "nodemon": "^2.0.7" + } +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/src/adapter.js b/templates/js/custom-copilot-rag-microsoft365/src/adapter.js new file mode 100644 index 0000000000..17ad23563e --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/adapter.js @@ -0,0 +1,51 @@ +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +const { + CloudAdapter, + ConfigurationBotFrameworkAuthentication, + ConfigurationServiceClientCredentialFactory, +} = require("botbuilder"); + +// This bot's main dialog. +const config = require("./config"); + +const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication( + {}, + new ConfigurationServiceClientCredentialFactory({ + MicrosoftAppId: config.botId, + MicrosoftAppPassword: process.env.BOT_PASSWORD, + MicrosoftAppType: "MultiTenant", + }) +); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about how bots work. +const adapter = new CloudAdapter(botFrameworkAuthentication); + +// Catch-all for errors. +const onTurnErrorHandler = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. + console.error(`\n [onTurnError] unhandled error: ${error}`); + + // Only send error message for user messages, not for other message types so the bot doesn't spam a channel or chat. + if (context.activity.type === "message") { + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendTraceActivity( + "OnTurnError Trace", + `${error}`, + "https://www.botframework.com/schemas/error", + "TurnError" + ); + + // Send a message to the user + await context.sendActivity("The bot encountered an error or bug."); + await context.sendActivity("To continue to run this bot, please fix the bot source code."); + } +}; + +// Set the onTurnError for the singleton CloudAdapter. +adapter.onTurnError = onTurnErrorHandler; + +module.exports = adapter; diff --git a/templates/js/custom-copilot-rag-microsoft365/src/app/app.js.tpl b/templates/js/custom-copilot-rag-microsoft365/src/app/app.js.tpl new file mode 100644 index 0000000000..22133c14a7 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/app/app.js.tpl @@ -0,0 +1,73 @@ +const { MemoryStorage } = require("botbuilder"); +const path = require("path"); +const config = require("../config"); + +// See https://aka.ms/teams-ai-library to learn more about the Teams AI library. +const { Application, ActionPlanner, OpenAIModel, PromptManager } = require("@microsoft/teams-ai"); +const { GraphDataSource } = require("./graphDataSource"); + +// Create AI components +const model = new OpenAIModel({ + {{#useOpenAI}} + apiKey: config.openAIKey, + defaultModel: config.openAIModelName, + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureApiKey: config.azureOpenAIKey, + azureDefaultDeployment: config.azureOpenAIDeploymentName, + azureEndpoint: config.azureOpenAIEndpoint, + {{/useAzureOpenAI}} + + useSystemMessages: true, + logRequests: true, +}); +const prompts = new PromptManager({ + promptsFolder: path.join(__dirname, "../prompts"), +}); +const planner = new ActionPlanner({ + model, + prompts, + defaultPrompt: "chat", +}); + +// Register your data source with planner +const graphDataSource = new GraphDataSource("graph-ai-search"); +planner.prompts.addDataSource(graphDataSource); + +// Define storage and application +const storage = new MemoryStorage(); +const app = new Application({ + storage, + ai: { + planner, + }, + authentication: { + settings: { + graph: { + scopes: ["Files.Read.All"], + msalConfig: { + auth: { + clientId: process.env.AAD_APP_CLIENT_ID, + clientSecret: process.env.AAD_APP_CLIENT_SECRET, + authority: `${process.env.AAD_APP_OAUTH_AUTHORITY_HOST}/${process.env.AAD_APP_TENANT_ID}` + } + }, + signInLink: `https://${process.env.BOT_DOMAIN}/auth-start.html`, + } + }, + autoSignIn: true, + } +}); + +app.authentication.get("graph").onUserSignInSuccess(async (context, state) => { + // Successfully logged in + await context.sendActivity("You are successfully logged in. You can send a new message to talk to the bot."); +}); + +app.authentication.get("graph").onUserSignInFailure(async (context, state, error) => { + // Failed to login + await context.sendActivity("Failed to login"); + await context.sendActivity(`Error message: ${error.message}`); +}); + +module.exports = app; diff --git a/templates/js/custom-copilot-rag-microsoft365/src/app/graphDataSource.js.tpl b/templates/js/custom-copilot-rag-microsoft365/src/app/graphDataSource.js.tpl new file mode 100644 index 0000000000..3add2571f7 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/app/graphDataSource.js.tpl @@ -0,0 +1,115 @@ +const { Client, ResponseType } = require("@microsoft/microsoft-graph-client"); + +/** + * A data source that searches through Graph API. + */ +class GraphDataSource { + /** + * Creates a new instance of the Graph DataSource instance. + */ + constructor(name) { + this.name = name; + } + + /** + * Renders the data source as a string of text. + */ + async renderData(context, memory, tokenizer, maxTokens) { + const query = memory.getValue("temp.input"); + if(!query) { + return { output: "", length: 0, tooLong: false }; + } + if (!this.graphClient) { + this.graphClient = Client.init({ + authProvider: (done) => { + done(null, memory.temp.authTokens["graph"]); + } + }); + } + let graphQuery = query; + if (query.toLocaleLowerCase().includes("perksplus")) { + graphQuery = "perksplus program"; + } else if (query.toLocaleLowerCase().includes("company") || query.toLocaleLowerCase().includes("history")) { + graphQuery = "company history"; + } else if (query.toLocaleLowerCase().includes("northwind") || query.toLocaleLowerCase().includes("health")) { + graphQuery = "northwind health"; + } + + const contentResults = []; + const response = await this.graphClient.api("/search/query").post({ + requests: [ + { + entityTypes: ["driveItem"], + query: { + // Search for markdown files in the user's OneDrive and SharePoint + // The supported file types are listed here: + // https://learn.microsoft.com/sharepoint/technical-reference/default-crawled-file-name-extensions-and-parsed-file-types + queryString: `${graphQuery}`, + }, + // This parameter is required only when searching with application permissions + // https://learn.microsoft.com/graph/search-concept-searchall + // region: "US", + }, + ], + }); + for (const value of response?.value ?? []) { + for (const hitsContainer of value?.hitsContainers ?? []) { + contentResults.push(...(hitsContainer?.hits ?? [])); + } + } + + // Add documents until you run out of tokens + let length = 0, + output = ""; + for (const result of contentResults) { + const rawContent = await this.downloadSharepointFile( + result.resource.webUrl + ); + if (!rawContent) { + continue; + } + let doc = `${rawContent}\n\n`; + let docLength = tokenizer.encode(doc).length; + const remainingTokens = maxTokens - (length + docLength); + if (remainingTokens <= 0) { + break; + } + + // Append do to output + output += doc; + length += docLength; + } + return { output: this.formatDocument(output), length: output.length, tooLong: false }; + } + + /** + * Formats the result string + */ + formatDocument(result) { + return `${result}`; + } + + // Download the file from SharePoint + // https://docs.microsoft.com/en-us/graph/api/driveitem-get-content + async downloadSharepointFile(contentUrl) { + const encodedUrl = this.encodeSharepointContentUrl(contentUrl); + const fileContentResponse = await this.graphClient + .api(`/shares/${encodedUrl}/driveItem/content`) + .responseType(ResponseType.TEXT) + .get(); + + return fileContentResponse; + } + + encodeSharepointContentUrl(webUrl) { + const byteData = Buffer.from(webUrl, "utf-8"); + const base64String = byteData.toString("base64"); + return ( + "u!" + base64String.replace("=", "").replace("/", "_").replace("+", "_") + ); + } +} + +module.exports = { + GraphDataSource, +}; \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/src/config.js.tpl b/templates/js/custom-copilot-rag-microsoft365/src/config.js.tpl new file mode 100644 index 0000000000..33035ba037 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/config.js.tpl @@ -0,0 +1,15 @@ +const config = { + botId: process.env.BOT_ID, + botPassword: process.env.BOT_PASSWORD, + {{#useOpenAI}} + openAIKey: process.env.OPENAI_API_KEY, + openAIModelName: "gpt-3.5-turbo", + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureOpenAIKey: process.env.AZURE_OPENAI_API_KEY, + azureOpenAIEndpoint: process.env.AZURE_OPENAI_ENDPOINT, + azureOpenAIDeploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME, + {{/useAzureOpenAI}} +}; + +module.exports = config; diff --git a/templates/js/custom-copilot-rag-microsoft365/src/data/Contoso Electronics_PerkPlus_Program.txt b/templates/js/custom-copilot-rag-microsoft365/src/data/Contoso Electronics_PerkPlus_Program.txt new file mode 100644 index 0000000000..1d97d5117e --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/data/Contoso Electronics_PerkPlus_Program.txt @@ -0,0 +1,36 @@ +# Contoso Electronics PerksPlus Program + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Overview +Introducing PerksPlus - the ultimate benefits program designed to support the health and wellness of employees. With PerksPlus, employees have the opportunity to expense up to $1000 for fitness-related programs, making it easier and more affordable to maintain a healthy lifestyle. PerksPlus is not only designed to support employees' physical health, but also their mental health. Regular exercise has been shown to reduce stress, improve mood, and enhance overall well-being. With PerksPlus, employees can invest in their health and wellness, while enjoying the peace of mind that comes with knowing they are getting the support they need to lead a healthy life. +What is Covered? + +PerksPlus covers a wide range of fitness activities, including but not limited to: +* Gym memberships +* Personal training sessions +* Yoga and Pilates classes +* Fitness equipment purchases +* Sports team fees +* Health retreats and spas +* Outdoor adventure activities (such as rock climbing, hiking, and kayaking) +* Group fitness classes (such as dance, martial arts, and cycling) +* Virtual fitness programs (such as online yoga and workout classes) + +In addition to the wide range of fitness activities covered by PerksPlus, the program also covers a variety of lessons and experiences that promote health and wellness. Some of the lessons covered under PerksPlus include: +* Skiing and snowboarding lessons +* Scuba diving lessons +* Surfing lessons +* Horseback riding lessons + +These lessons provide employees with the opportunity to try new things, challenge themselves, and improve their physical skills. They are also a great way to relieve stress and have fun while staying active. + +With PerksPlus, employees can choose from a variety of fitness programs to suit their individual needs and preferences. Whether you're looking to improve your physical fitness, reduce stress, or just have some fun, PerksPlus has you covered. + +## What is Not Covered? +In addition to the wide range of activities covered by PerksPlus, there is also a list of things that are not +covered under the program. These include but are not limited to: +* Non-fitness related expenses +* Medical treatments and procedures +* Travel expenses (unless related to a fitness program) +* Food and supplements \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Company_Overview.txt b/templates/js/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Company_Overview.txt new file mode 100644 index 0000000000..6878a8e204 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Company_Overview.txt @@ -0,0 +1,48 @@ +# Contoso Electronics Company Overview + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## History + +Contoso Electronics, a pioneering force in the tech industry, was founded in 1985 by visionary entrepreneurs with a passion for innovation. Over the years, the company has played a pivotal role in shaping the landscape of consumer electronics. + +| Year | Milestone | +|------|-----------| +| 1985 | Company founded with a focus on cutting-edge technology | +| 1990 | Launched the first-ever handheld personal computer | +| 2000 | Introduced groundbreaking advancements in AI and robotics | +| 2015 | Expansion into sustainable and eco-friendly product lines | + +## Company Overview + +At Contoso Electronics, we take pride in fostering a dynamic and inclusive workplace. Our dedicated team of experts collaborates to create innovative solutions that empower and connect people globally. + +### Core Values + +- **Innovation:** Constantly pushing the boundaries of technology. +- **Diversity:** Embracing different perspectives for creative excellence. +- **Sustainability:** Committed to eco-friendly practices in our products. + +## Vacation Perks + +We believe in work-life balance and understand the importance of well-deserved breaks. Our vacation perks are designed to help our employees recharge and return with renewed enthusiasm. + +| Vacation Tier | Duration | Additional Benefits | +|---------------|----------|---------------------| +| Standard | 2 weeks | Health and wellness stipend | +| Senior | 4 weeks | Travel vouchers for a dream destination | +| Executive | 6 weeks | Luxury resort getaway with family | + +## Employee Recognition + +Recognizing the hard work and dedication of our employees is at the core of our culture. Here are some ways we celebrate achievements: + +- Monthly "Innovator of the Month" awards +- Annual gala with awards for outstanding contributions +- Team-building retreats for high-performing departments + +## Join Us! + +Contoso Electronics is always on the lookout for talented individuals who share our passion for innovation. If you're ready to be part of a dynamic team shaping the future of technology, check out our [careers page](http://www.contoso.com) for exciting opportunities. + +[Learn more about Contoso Electronics!](http://www.contoso.com) diff --git a/templates/js/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Plan_Benefits.txt b/templates/js/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Plan_Benefits.txt new file mode 100644 index 0000000000..9da5c6429d --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Plan_Benefits.txt @@ -0,0 +1,37 @@ +# Contoso Electronics Plan and Benefit Packages + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Northwind Health Plus + +Northwind Health Plus is a comprehensive plan that provides comprehensive coverage for medical, vision, and dental services. This plan also offers prescription drug coverage, mental health and substance abuse coverage, and coverage for preventive care services. With Northwind Health Plus, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. + +This plan also offers coverage for emergency services, both in-network and out-of-network. + +## Northwind Standard + +Northwind Standard is a basic plan that provides coverage for medical, vision, and dental services. This plan also offers coverage for preventive care services, as well as prescription drug coverage. With Northwind Standard, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. This plan does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +## Comparison of Plans + +Both plans offer coverage for routine physicals, well-child visits, immunizations, and other preventive care services. The plans also cover preventive care services such as mammograms, colonoscopies, and other cancer screenings. + +Northwind Health Plus offers more comprehensive coverage than Northwind Standard. This plan offers coverage for emergency services, both in-network and out-of-network, as well as mental health and substance abuse coverage. Northwind Standard does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +Both plans offer coverage for prescription drugs. Northwind Health Plus offers a wider range of prescription drug coverage than Northwind Standard. Northwind Health Plus covers generic, brand-name, and specialty drugs, while Northwind Standard only covers generic and brand-name drugs. + +Both plans offer coverage for vision and dental services. Northwind Health Plus offers coverage for vision exams, glasses, and contact lenses, as well as dental exams, cleanings, and fillings. Northwind Standard only offers coverage for vision exams and glasses. + +Both plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays, doctor visits, lab tests, and X-rays. Northwind Standard only offers coverage for doctor visits and lab tests. + +Northwind Health Plus is a comprehensive plan that offers more coverage than Northwind Standard. Northwind Health Plus offers coverage for emergency services, mental health and substance abuse coverage, and out-of-network services, while Northwind Standard does not. Northwind Health Plus also offers a wider range of prescription drug coverage than Northwind Standard. Both plans offer coverage for vision and dental services, as well as medical services. + +## Cost Comparison + +Contoso Electronics deducts the employee's portion of the healthcare cost from each paycheck. This means that the cost of the health insurance will be spread out over the course of the year, rather than being paid in one lump sum. The employee's portion of the cost will be calculated based on the selected health plan and the number of people covered by the insurance. The table below shows a cost comparison between the different health plans offered by Contoso Electronics + +| | Northwind Standard | NorthWind Health Plus | +|---------------|----------|---------------------| +| Employee Only | $45.00 | $55.00 | +| Employee +1 | $65.00 | $71.00 | +| Employee +2 or more | $78.00 | $89.00 | \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/src/index.js b/templates/js/custom-copilot-rag-microsoft365/src/index.js new file mode 100644 index 0000000000..98912b6f41 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/index.js @@ -0,0 +1,33 @@ +// Import required packages +const restify = require("restify"); + +// This bot's adapter +const adapter = require("./adapter"); + +// This bot's main dialog. +const app = require("./app/app"); +const path = require("path"); + +// Create HTTP server. +const server = restify.createServer(); +server.use(restify.plugins.bodyParser()); + +server.listen(process.env.port || process.env.PORT || 3978, () => { + console.log(`\nBot Started, ${server.name} listening to ${server.url}`); +}); + +// Listen for incoming server requests. +server.post("/api/messages", async (req, res) => { + // Route received a request to adapter for processing + await adapter.process(req, res, async (context) => { + // Dispatch to application for routing + await app.run(context); + }); +}); + +server.get( + "/auth-:name(start|end).html", + restify.plugins.serveStatic({ + directory: path.join(__dirname, "public"), + }) +); diff --git a/templates/js/custom-copilot-rag-microsoft365/src/prompts/chat/config.json b/templates/js/custom-copilot-rag-microsoft365/src/prompts/chat/config.json new file mode 100644 index 0000000000..c9e6987a1e --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/prompts/chat/config.json @@ -0,0 +1,22 @@ +{ + "schema": 1.1, + "description": "Chat with Teams RAG.", + "type": "completion", + "completion": { + "completion_type": "chat", + "include_history": true, + "include_input": true, + "max_input_tokens": 2800, + "max_tokens": 1000, + "temperature": 0.9, + "top_p": 0.0, + "presence_penalty": 0.6, + "frequency_penalty": 0.0, + "stop_sequences": [] + }, + "augmentation": { + "data_sources": { + "graph-ai-search": 1200 + } + } +} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/src/prompts/chat/skprompt.txt b/templates/js/custom-copilot-rag-microsoft365/src/prompts/chat/skprompt.txt new file mode 100644 index 0000000000..2a2ebee5a3 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/prompts/chat/skprompt.txt @@ -0,0 +1,3 @@ +The following is a conversation with an AI assistant, who is an expert on answering questions over the given context. +Responses should be in a short journalistic style with no more than 80 words. +Use the context provided in the `` tags as the source for your answers. \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/src/public/auth-end.html b/templates/js/custom-copilot-rag-microsoft365/src/public/auth-end.html new file mode 100644 index 0000000000..07fe2fa3b2 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/public/auth-end.html @@ -0,0 +1,65 @@ + + + Login End Page + + + + + +
+ + + diff --git a/templates/js/custom-copilot-rag-microsoft365/src/public/auth-start.html b/templates/js/custom-copilot-rag-microsoft365/src/public/auth-start.html new file mode 100644 index 0000000000..4a2d258804 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/src/public/auth-start.html @@ -0,0 +1,177 @@ + + + + + Login Start Page + + + + + + + diff --git a/templates/js/custom-copilot-rag-microsoft365/teamsapp.local.yml.tpl b/templates/js/custom-copilot-rag-microsoft365/teamsapp.local.yml.tpl new file mode 100644 index 0000000000..3e7e203b0e --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/teamsapp.local.yml.tpl @@ -0,0 +1,113 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +provision: + - uses: aadApp/create # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty + with: + name: {{appName}}-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. + generateClientSecret: true # If the value is false, the action will not generate client secret for you + signInAudience: "AzureADMyOrg" # Authenticate users with a Microsoft work or school account in your organization's Azure AD tenant (for example, single tenant). + writeToEnvironmentFile: # Write the information of created resources into environment file for the specified environment variable(s). + clientId: AAD_APP_CLIENT_ID + clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file + objectId: AAD_APP_OBJECT_ID + tenantId: AAD_APP_TENANT_ID + authority: AAD_APP_OAUTH_AUTHORITY + authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST + + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create + with: + botId: ${{BOT_ID}} + name: {{appName}} + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + - uses: aadApp/update # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update. + with: + manifestPath: ./aad.manifest.json # Relative path to teamsfx folder. Environment variables in manifest will be replaced before apply to AAD app + outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install --no-audit + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs + envs: + BOT_ID: ${{BOT_ID}} + BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + BOT_DOMAIN: ${{BOT_DOMAIN}} + AAD_APP_CLIENT_ID: ${{AAD_APP_CLIENT_ID}} + AAD_APP_CLIENT_SECRET: ${{SECRET_AAD_APP_CLIENT_SECRET}} + AAD_APP_TENANT_ID: ${{AAD_APP_TENANT_ID}} + AAD_APP_OAUTH_AUTHORITY_HOST: ${{AAD_APP_OAUTH_AUTHORITY_HOST}} + {{#useOpenAI}} + OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/js/custom-copilot-rag-microsoft365/teamsapp.yml.tpl b/templates/js/custom-copilot-rag-microsoft365/teamsapp.yml.tpl new file mode 100644 index 0000000000..4f332829a2 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/teamsapp.yml.tpl @@ -0,0 +1,163 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: + - uses: aadApp/create # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty + with: + name: {{appName}}-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. + generateClientSecret: true # If the value is false, the action will not generate client secret for you + signInAudience: "AzureADMyOrg" # Authenticate users with a Microsoft work or school account in your organization's Azure AD tenant (for example, single tenant). + writeToEnvironmentFile: # Write the information of created resources into environment file for the specified environment variable(s). + clientId: AAD_APP_CLIENT_ID + clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file + objectId: AAD_APP_OBJECT_ID + tenantId: AAD_APP_TENANT_ID + authority: AAD_APP_OAUTH_AUTHORITY + authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST + + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Teams Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + - uses: aadApp/update # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update. + with: + manifestPath: ./aad.manifest.json # Relative path to teamsfx folder. Environment variables in manifest will be replaced before apply to AAD app + outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsapp deploy' is executed +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install + - uses: cli/runNpmCommand + name: build app + with: + args: run build --if-present + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: . + # Ignore file location, leave blank will ignore nothing + ignoreFile: .webappignore + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + +# Triggered when 'teamsapp publish' is executed +publish: + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Publish the app to + # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps) + # for review and approval + - uses: teamsApp/publishAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + publishedAppId: TEAMS_APP_PUBLISHED_APP_ID diff --git a/templates/js/custom-copilot-rag-microsoft365/web.config b/templates/js/custom-copilot-rag-microsoft365/web.config new file mode 100644 index 0000000000..0c09f2f869 --- /dev/null +++ b/templates/js/custom-copilot-rag-microsoft365/web.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/js/default-bot/.gitignore b/templates/js/default-bot/.gitignore index 1cbb2aad8d..07a76d44b3 100644 --- a/templates/js/default-bot/.gitignore +++ b/templates/js/default-bot/.gitignore @@ -14,3 +14,6 @@ node_modules/ .env .deployment .DS_Store + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/js/default-bot/.webappignore b/templates/js/default-bot/.webappignore index a4548f4240..f79d01ac12 100644 --- a/templates/js/default-bot/.webappignore +++ b/templates/js/default-bot/.webappignore @@ -25,3 +25,4 @@ teamsapp.*.yml /node_modules/typescript /appPackage/ /infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/default-bot/teamsapp.testtool.yml b/templates/js/default-bot/teamsapp.testtool.yml index bb912b9a9d..3217c43522 100644 --- a/templates/js/default-bot/teamsapp.testtool.yml +++ b/templates/js/default-bot/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/link-unfurling/README.md b/templates/js/link-unfurling/README.md.tpl similarity index 82% rename from templates/js/link-unfurling/README.md rename to templates/js/link-unfurling/README.md.tpl index f5c123dec8..235f1d152d 100644 --- a/templates/js/link-unfurling/README.md +++ b/templates/js/link-unfurling/README.md.tpl @@ -2,21 +2,35 @@ This template showcases an app that unfurls a link into an adaptive card when URLs with a particular domain are pasted into the compose message area in Microsoft Teams or email body in Outlook. +{{#enableMETestToolByDefault}} +![hero-image](https://aka.ms/teams-app-test-tool-link-unfurling-hero-image) +{{/enableMETestToolByDefault}} +{{^enableMETestToolByDefault}} ![hero-image](https://aka.ms/teamsfx-link-unfurling-hero-image) +{{/enableMETestToolByDefault}} ## Get Started with the Link Unfurling app > **Prerequisites** > > - [Node.js](https://nodejs.org/), supported versions: 16, 18 +{{^enableMETestToolByDefault}} > - A Microsoft 365 account. If you do not have Microsoft 365 account, apply one from [Microsoft 365 developer program](https://developer.microsoft.com/microsoft-365/dev-program) +{{/enableMETestToolByDefault}} > - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [TeamsFx CLI](https://aka.ms/teamsfx-toolkit-cli) 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#enableMETestToolByDefault}} +2. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. +3. The browser will pop up to open Teams App Test Tool. +4. Click the "+" button in the input box, select "Link Unfurling" and paste a link ending with `.botframework.com`. You should see an adaptive card unfurled. Click `Send to Conversation` to send it to the current chat or channel. +{{/enableMETestToolByDefault}} +{{^enableMETestToolByDefault}} 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. 3. Press F5 to start debugging which launches your app in Teams or Outlook using a web browser by select a target Microsoft application: `Debug in Teams`, `Debug in Outlook` and click the `Run and Debug` green arrow button. 4. When Teams or Outlook launches in the browser, select the Add button in the dialog to install your app to Teams. 5. Paste a link ending with `.botframework.com` into compose message area in Teams or email body in Outlook. You should see an adaptive card unfurled. +{{/enableMETestToolByDefault}} ## What's included in the template @@ -24,6 +38,7 @@ This template showcases an app that unfurls a link into an adaptive card when UR | -------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `teamsapp.yml` | Main project file describes your application configuration and defines the set of actions to run in each lifecycle stages | | `teamsapp.local.yml` | This overrides `teamsapp.yml` with actions that enable local execution and debugging | +| `teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool. | | `.vscode/` | VSCode files for local debug | | `src/` | The source code for the link unfurling application | | `appPackage/` | Templates for the Teams application manifest | diff --git a/templates/js/link-unfurling/teamsapp.testtool.yml b/templates/js/link-unfurling/teamsapp.testtool.yml index eaf11c0c74..3217c43522 100644 --- a/templates/js/link-unfurling/teamsapp.testtool.yml +++ b/templates/js/link-unfurling/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.2.0-alpha + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/m365-message-extension/README.md b/templates/js/m365-message-extension/README.md.tpl similarity index 87% rename from templates/js/m365-message-extension/README.md rename to templates/js/m365-message-extension/README.md.tpl index c609776bea..1d2b7bccc3 100644 --- a/templates/js/m365-message-extension/README.md +++ b/templates/js/m365-message-extension/README.md.tpl @@ -9,12 +9,23 @@ This app template is a search-based [message extension](https://docs.microsoft.c > To run the template in your local dev machine, you will need: > > - [Node.js](https://nodejs.org/), supported versions: 16, 18 +{{^enableMETestToolByDefault}} > - A [Microsoft 365 account for development](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) > - [Set up your dev environment for extending Teams apps across Microsoft 365](https://aka.ms/teamsfx-m365-apps-prerequisites) > Please note that after you enrolled your developer tenant in Office 365 Target Release, it may take couple days for the enrollment to take effect. +{{/enableMETestToolByDefault}} > - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#enableMETestToolByDefault}} +2. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. Select `Debug in Test Tool (Preview)`. +3. To trigger the Message Extension, you can click the `+` in compose message area and select `Search Command` + +**Congratulations**! You are running an application that can now search npm registries in Teams App Test Tool. + +![Search app demo](https://github.com/OfficeDev/TeamsFx/assets/9698542/5275e5bc-492f-4365-b602-5803938a9780) +{{/enableMETestToolByDefault}} +{{^enableMETestToolByDefault}} 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. 3. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. 4. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams. @@ -25,6 +36,7 @@ This app template is a search-based [message extension](https://docs.microsoft.c **Congratulations**! You are running an application that can now search npm registries in Teams and Outlook. ![Search app demo](https://github.com/OfficeDev/TeamsFx/assets/25220706/27fefae9-c51f-49af-a175-c8c9d5a71af0) +{{/enableMETestToolByDefault}} ## What's included in the template @@ -49,6 +61,7 @@ The following are Teams Toolkit specific project files. You can [visit a complet | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `teamsapp.yml` | This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | | `teamsapp.local.yml` | This overrides `teamsapp.yml` with actions that enable local execution and debugging. | +| `teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool. | ## Extend the template diff --git a/templates/js/m365-message-extension/teamsapp.testtool.yml b/templates/js/m365-message-extension/teamsapp.testtool.yml index eaf11c0c74..3217c43522 100644 --- a/templates/js/m365-message-extension/teamsapp.testtool.yml +++ b/templates/js/m365-message-extension/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.2.0-alpha + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/message-extension-action/.vscode/launch.json.tpl b/templates/js/message-extension-action/.vscode/launch.json.tpl index a0369287c8..fb267e00ad 100644 --- a/templates/js/message-extension-action/.vscode/launch.json.tpl +++ b/templates/js/message-extension-action/.vscode/launch.json.tpl @@ -76,7 +76,7 @@ "group": "group 0: Teams App Test Tool", {{/enableMETestToolByDefault}} {{^enableMETestToolByDefault}} - "group": "group 3: Teams App Test Tool", + "group": "group 2: Teams App Test Tool", {{/enableMETestToolByDefault}} "order": 1 }, @@ -90,7 +90,7 @@ ], "preLaunchTask": "Start Teams App Locally", "presentation": { - "group": "all", + "group": "group 1: Teams", "order": 1 }, "stopAll": true @@ -103,7 +103,7 @@ ], "preLaunchTask": "Start Teams App Locally", "presentation": { - "group": "all", + "group": "group 1: Teams", "order": 2 }, "stopAll": true diff --git a/templates/js/message-extension-action/README.md b/templates/js/message-extension-action/README.md.tpl similarity index 87% rename from templates/js/message-extension-action/README.md rename to templates/js/message-extension-action/README.md.tpl index 5ab23e8e25..756328c4c8 100644 --- a/templates/js/message-extension-action/README.md +++ b/templates/js/message-extension-action/README.md.tpl @@ -11,10 +11,21 @@ This app template implements action command that allows you to present your user > To run the template in your local dev machine, you will need: > > - [Node.js](https://nodejs.org/), supported versions: 16, 18 +{{^enableMETestToolByDefault}} > - A [Microsoft 365 account for development](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) +{{/enableMETestToolByDefault}} > - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#enableMETestToolByDefault}} +2. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. +3. To trigger the action command, you can click the `+` under compose message area and select `Action Command`. + +**Congratulations**! You are running an application that can share information in rich format by creating an Adaptive Card in Teams App Test Tool. + +![action-ME](https://github.com/OfficeDev/TeamsFx/assets/9698542/c0afbd89-7fbb-4e73-98a2-f018be4ca88c) +{{/enableMETestToolByDefault}} +{{^enableMETestToolByDefault}} 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. 3. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. 4. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams. @@ -23,6 +34,7 @@ This app template implements action command that allows you to present your user **Congratulations**! You are running an application that can share information in rich format by creating an Adaptive Card in Teams. ![action-ME](https://github.com/OfficeDev/TeamsFx/assets/25220706/378ea4d7-9332-4aec-9f85-59891d086b80) +{{/enableMETestToolByDefault}} ## What's included in the template @@ -47,6 +59,7 @@ The following are Teams Toolkit specific project files. You can [visit a complet | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `teamsapp.yml` | This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | | `teamsapp.local.yml` | This overrides `teamsapp.yml` with actions that enable local execution and debugging. | +| `teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool. | ## Extend the template diff --git a/templates/js/message-extension-action/teamsapp.testtool.yml b/templates/js/message-extension-action/teamsapp.testtool.yml index eaf11c0c74..3217c43522 100644 --- a/templates/js/message-extension-action/teamsapp.testtool.yml +++ b/templates/js/message-extension-action/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.2.0-alpha + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/message-extension/README.md b/templates/js/message-extension/README.md.tpl similarity index 84% rename from templates/js/message-extension/README.md rename to templates/js/message-extension/README.md.tpl index f801d9eb18..8ea24e8532 100644 --- a/templates/js/message-extension/README.md +++ b/templates/js/message-extension/README.md.tpl @@ -15,10 +15,24 @@ This app template has a search command, an action command and a link unfurling. > To run the template in your local dev machine, you will need: > > - [Node.js](https://nodejs.org/), supported versions: 16, 18 +{{^enableMETestToolByDefault}} > - A [Microsoft 365 account for development](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) +{{/enableMETestToolByDefault}} > - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#enableMETestToolByDefault}} +2. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. +3. To trigger the Message Extension to invoke commands: + 1. To trigger search commands, click the `+` in compose message area and select `Search command`. + 2. To trigger action commands, click the `+` in compose message area or `...` above a message and select `Action command`. + 3. To trigger link unfurling, click the `+` in compose message area and select `Link unfurling`. + +![Search app demo](https://github.com/OfficeDev/TeamsFx/assets/9698542/5275e5bc-492f-4365-b602-5803938a9780) + +![action-ME](https://github.com/OfficeDev/TeamsFx/assets/9698542/c0afbd89-7fbb-4e73-98a2-f018be4ca88c) +{{/enableMETestToolByDefault}} +{{^enableMETestToolByDefault}} 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. 3. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. 4. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams. @@ -27,7 +41,10 @@ This app template has a search command, an action command and a link unfurling. 2. In Outlook: click the `More apps` icon under compose email area to find your message extension. Only search command and link unfurling works in Outlook. 6. Paste a link ending with `.botframework.com` into compose message area in Teams or email body in Outlook. You should see an adaptive card unfurled. +![Search app demo](https://user-images.githubusercontent.com/11220663/167868361-40ffaaa3-0300-4313-ae22-0f0bab49c329.png) + ![action-ME](https://github.com/OfficeDev/TeamsFx/assets/25220706/378ea4d7-9332-4aec-9f85-59891d086b80) +{{/enableMETestToolByDefault}} ## What's included in the template @@ -51,6 +68,7 @@ The following are Teams Toolkit specific project files. You can [visit a complet | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `teamsapp.yml` | This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | | `teamsapp.local.yml` | This overrides `teamsapp.yml` with actions that enable local execution and debugging. | +| `teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool. | ## Extend the template diff --git a/templates/js/message-extension/teamsapp.testtool.yml b/templates/js/message-extension/teamsapp.testtool.yml index eaf11c0c74..3217c43522 100644 --- a/templates/js/message-extension/teamsapp.testtool.yml +++ b/templates/js/message-extension/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.2.0-alpha + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/notification-http-timer-trigger/teamsapp.testtool.yml b/templates/js/notification-http-timer-trigger/teamsapp.testtool.yml index d0ae53af63..75484d7559 100644 --- a/templates/js/notification-http-timer-trigger/teamsapp.testtool.yml +++ b/templates/js/notification-http-timer-trigger/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester func: version: ~4.0.5174 diff --git a/templates/js/notification-http-trigger/teamsapp.testtool.yml b/templates/js/notification-http-trigger/teamsapp.testtool.yml index d0ae53af63..75484d7559 100644 --- a/templates/js/notification-http-trigger/teamsapp.testtool.yml +++ b/templates/js/notification-http-trigger/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester func: version: ~4.0.5174 diff --git a/templates/js/notification-restify/.appserviceignore b/templates/js/notification-restify/.appserviceignore index e734b956d0..2d8a3ad185 100644 --- a/templates/js/notification-restify/.appserviceignore +++ b/templates/js/notification-restify/.appserviceignore @@ -25,3 +25,4 @@ teamsapp.*.yml /node_modules/typescript /appPackage/ /infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/notification-restify/.gitignore b/templates/js/notification-restify/.gitignore index 01faebf252..dfb975ac86 100644 --- a/templates/js/notification-restify/.gitignore +++ b/templates/js/notification-restify/.gitignore @@ -21,3 +21,6 @@ lib/ .localConfigs .notification.localstore.json .notification.testtoolstore.json + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/js/notification-restify/teamsapp.testtool.yml b/templates/js/notification-restify/teamsapp.testtool.yml index bb912b9a9d..3217c43522 100644 --- a/templates/js/notification-restify/teamsapp.testtool.yml +++ b/templates/js/notification-restify/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/js/notification-timer-trigger/teamsapp.testtool.yml b/templates/js/notification-timer-trigger/teamsapp.testtool.yml index d0ae53af63..75484d7559 100644 --- a/templates/js/notification-timer-trigger/teamsapp.testtool.yml +++ b/templates/js/notification-timer-trigger/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester func: version: ~4.0.5174 diff --git a/templates/js/sso-tab-with-obo-flow/package.json.tpl b/templates/js/sso-tab-with-obo-flow/package.json.tpl index 8e544ad213..a899d1239b 100644 --- a/templates/js/sso-tab-with-obo-flow/package.json.tpl +++ b/templates/js/sso-tab-with-obo-flow/package.json.tpl @@ -17,7 +17,7 @@ "react-scripts": "^5.0.1" }, "devDependencies": { - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@types/react-router-dom": "^5.3.3", diff --git a/templates/js/workflow/.appserviceignore b/templates/js/workflow/.appserviceignore index 24f5be9bcf..81e4ebc4dd 100644 --- a/templates/js/workflow/.appserviceignore +++ b/templates/js/workflow/.appserviceignore @@ -25,3 +25,4 @@ teamsapp.*.yml /node_modules/typescript /appPackage/ /infra/ +/devTools/ \ No newline at end of file diff --git a/templates/js/workflow/.gitignore b/templates/js/workflow/.gitignore index 01faebf252..dfb975ac86 100644 --- a/templates/js/workflow/.gitignore +++ b/templates/js/workflow/.gitignore @@ -21,3 +21,6 @@ lib/ .localConfigs .notification.localstore.json .notification.testtoolstore.json + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/js/workflow/teamsapp.testtool.yml b/templates/js/workflow/teamsapp.testtool.yml index bb912b9a9d..3217c43522 100644 --- a/templates/js/workflow/teamsapp.testtool.yml +++ b/templates/js/workflow/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/python/custom-copilot-basic/.gitignore b/templates/python/custom-copilot-basic/.gitignore index ec1f6285c6..7f6c83142e 100644 --- a/templates/python/custom-copilot-basic/.gitignore +++ b/templates/python/custom-copilot-basic/.gitignore @@ -12,4 +12,6 @@ __pycache__/ # others .deployment/ node_modules/ -devTools/*.log \ No newline at end of file + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/python/custom-copilot-basic/.webappignore b/templates/python/custom-copilot-basic/.webappignore index 01a10bb819..63b9af103f 100644 --- a/templates/python/custom-copilot-basic/.webappignore +++ b/templates/python/custom-copilot-basic/.webappignore @@ -6,4 +6,5 @@ __pycache__/ README.md teamsapp.yml teamsapp.local.yml -teamsapp.testtool.yml \ No newline at end of file +teamsapp.testtool.yml +/devTools/ \ No newline at end of file diff --git a/templates/python/custom-copilot-basic/env/.env.dev.user.tpl b/templates/python/custom-copilot-basic/env/.env.dev.user.tpl index a13ba9fe10..10cd616ae1 100644 --- a/templates/python/custom-copilot-basic/env/.env.dev.user.tpl +++ b/templates/python/custom-copilot-basic/env/.env.dev.user.tpl @@ -17,7 +17,12 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY= {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} diff --git a/templates/python/custom-copilot-basic/env/.env.local.user.tpl b/templates/python/custom-copilot-basic/env/.env.local.user.tpl index 87043cc118..6a63206dbb 100644 --- a/templates/python/custom-copilot-basic/env/.env.local.user.tpl +++ b/templates/python/custom-copilot-basic/env/.env.local.user.tpl @@ -18,7 +18,12 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY= {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} diff --git a/templates/python/custom-copilot-basic/env/.env.testtool.user.tpl b/templates/python/custom-copilot-basic/env/.env.testtool.user.tpl index 23f2a3b952..76d74f19c2 100644 --- a/templates/python/custom-copilot-basic/env/.env.testtool.user.tpl +++ b/templates/python/custom-copilot-basic/env/.env.testtool.user.tpl @@ -17,7 +17,12 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY= {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} diff --git a/templates/python/custom-copilot-basic/teamsapp.testtool.yml.tpl b/templates/python/custom-copilot-basic/teamsapp.testtool.yml.tpl index 94ce2460ca..74a4f982b7 100644 --- a/templates/python/custom-copilot-basic/teamsapp.testtool.yml.tpl +++ b/templates/python/custom-copilot-basic/teamsapp.testtool.yml.tpl @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Generate runtime environment variables diff --git a/templates/python/custom-copilot-rag-azure-ai-search/.gitignore b/templates/python/custom-copilot-rag-azure-ai-search/.gitignore index ec1f6285c6..7f6c83142e 100644 --- a/templates/python/custom-copilot-rag-azure-ai-search/.gitignore +++ b/templates/python/custom-copilot-rag-azure-ai-search/.gitignore @@ -12,4 +12,6 @@ __pycache__/ # others .deployment/ node_modules/ -devTools/*.log \ No newline at end of file + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/python/custom-copilot-rag-azure-ai-search/env/.env.dev.user.tpl b/templates/python/custom-copilot-rag-azure-ai-search/env/.env.dev.user.tpl index 947e5a84e4..7af9d54b91 100644 --- a/templates/python/custom-copilot-rag-azure-ai-search/env/.env.dev.user.tpl +++ b/templates/python/custom-copilot-rag-azure-ai-search/env/.env.dev.user.tpl @@ -17,14 +17,24 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY= {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT='{{{azureOpenAIEmbeddingDeploymentName}}}' +{{/azureOpenAIEmbeddingDeploymentName}} +{{^azureOpenAIEmbeddingDeploymentName}} AZURE_OPENAI_EMBEDDING_DEPLOYMENT= +{{/azureOpenAIEmbeddingDeploymentName}} {{/useAzureOpenAI}} SECRET_AZURE_SEARCH_KEY= diff --git a/templates/python/custom-copilot-rag-azure-ai-search/env/.env.local.user.tpl b/templates/python/custom-copilot-rag-azure-ai-search/env/.env.local.user.tpl index 0d32eb7c2c..14169d1be3 100644 --- a/templates/python/custom-copilot-rag-azure-ai-search/env/.env.local.user.tpl +++ b/templates/python/custom-copilot-rag-azure-ai-search/env/.env.local.user.tpl @@ -18,14 +18,24 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY= {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT='{{{azureOpenAIEmbeddingDeploymentName}}}' +{{/azureOpenAIEmbeddingDeploymentName}} +{{^azureOpenAIEmbeddingDeploymentName}} AZURE_OPENAI_EMBEDDING_DEPLOYMENT= +{{/azureOpenAIEmbeddingDeploymentName}} {{/useAzureOpenAI}} SECRET_AZURE_SEARCH_KEY= diff --git a/templates/python/custom-copilot-rag-azure-ai-search/env/.env.testtool.user.tpl b/templates/python/custom-copilot-rag-azure-ai-search/env/.env.testtool.user.tpl index 0d32eb7c2c..14169d1be3 100644 --- a/templates/python/custom-copilot-rag-azure-ai-search/env/.env.testtool.user.tpl +++ b/templates/python/custom-copilot-rag-azure-ai-search/env/.env.testtool.user.tpl @@ -18,14 +18,24 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY= {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT='{{{azureOpenAIEmbeddingDeploymentName}}}' +{{/azureOpenAIEmbeddingDeploymentName}} +{{^azureOpenAIEmbeddingDeploymentName}} AZURE_OPENAI_EMBEDDING_DEPLOYMENT= +{{/azureOpenAIEmbeddingDeploymentName}} {{/useAzureOpenAI}} SECRET_AZURE_SEARCH_KEY= diff --git a/templates/python/custom-copilot-rag-azure-ai-search/teamsapp.testtool.yml.tpl b/templates/python/custom-copilot-rag-azure-ai-search/teamsapp.testtool.yml.tpl index bfc9c65191..8b2196c46c 100644 --- a/templates/python/custom-copilot-rag-azure-ai-search/teamsapp.testtool.yml.tpl +++ b/templates/python/custom-copilot-rag-azure-ai-search/teamsapp.testtool.yml.tpl @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Generate runtime environment variables diff --git a/templates/python/custom-copilot-rag-customize/.gitignore b/templates/python/custom-copilot-rag-customize/.gitignore index 58fd2596d0..2d6d21d99d 100644 --- a/templates/python/custom-copilot-rag-customize/.gitignore +++ b/templates/python/custom-copilot-rag-customize/.gitignore @@ -12,4 +12,6 @@ __pycache__/ # others .deployment/ node_modules/ -devTools/*.log + +# Dev tool directories +/devTools/ diff --git a/templates/python/custom-copilot-rag-customize/env/.env.dev.user.tpl b/templates/python/custom-copilot-rag-customize/env/.env.dev.user.tpl index a13ba9fe10..10cd616ae1 100644 --- a/templates/python/custom-copilot-rag-customize/env/.env.dev.user.tpl +++ b/templates/python/custom-copilot-rag-customize/env/.env.dev.user.tpl @@ -17,7 +17,12 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY= {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} diff --git a/templates/python/custom-copilot-rag-customize/env/.env.local.user.tpl b/templates/python/custom-copilot-rag-customize/env/.env.local.user.tpl index 87043cc118..6a63206dbb 100644 --- a/templates/python/custom-copilot-rag-customize/env/.env.local.user.tpl +++ b/templates/python/custom-copilot-rag-customize/env/.env.local.user.tpl @@ -18,7 +18,12 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY= {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} diff --git a/templates/python/custom-copilot-rag-customize/env/.env.testtool.user.tpl b/templates/python/custom-copilot-rag-customize/env/.env.testtool.user.tpl index 23f2a3b952..76d74f19c2 100644 --- a/templates/python/custom-copilot-rag-customize/env/.env.testtool.user.tpl +++ b/templates/python/custom-copilot-rag-customize/env/.env.testtool.user.tpl @@ -17,7 +17,12 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY= {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} diff --git a/templates/python/custom-copilot-rag-customize/teamsapp.testtool.yml.tpl b/templates/python/custom-copilot-rag-customize/teamsapp.testtool.yml.tpl index fc4f31a8bd..83f018ab56 100644 --- a/templates/python/custom-copilot-rag-customize/teamsapp.testtool.yml.tpl +++ b/templates/python/custom-copilot-rag-customize/teamsapp.testtool.yml.tpl @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Generate runtime environment variables diff --git a/templates/ts/ai-assistant-bot/.webappignore b/templates/ts/ai-assistant-bot/.webappignore index 3e9dfd4d90..1759231814 100644 --- a/templates/ts/ai-assistant-bot/.webappignore +++ b/templates/ts/ai-assistant-bot/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/ai-assistant-bot/package.json.tpl b/templates/ts/ai-assistant-bot/package.json.tpl index 51b800c747..bbda4b843b 100644 --- a/templates/ts/ai-assistant-bot/package.json.tpl +++ b/templates/ts/ai-assistant-bot/package.json.tpl @@ -33,7 +33,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/ai-assistant-bot/teamsapp.testtool.yml b/templates/ts/ai-assistant-bot/teamsapp.testtool.yml index e53a7bc322..51495063dc 100644 --- a/templates/ts/ai-assistant-bot/teamsapp.testtool.yml +++ b/templates/ts/ai-assistant-bot/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/ai-bot/.gitignore b/templates/ts/ai-bot/.gitignore index a0757d5341..a58569ebf7 100644 --- a/templates/ts/ai-bot/.gitignore +++ b/templates/ts/ai-bot/.gitignore @@ -17,4 +17,7 @@ node_modules/ .DS_Store # build -lib/ \ No newline at end of file +lib/ + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/ts/ai-bot/.webappignore b/templates/ts/ai-bot/.webappignore index 18a015a2a3..f79d01ac12 100644 --- a/templates/ts/ai-bot/.webappignore +++ b/templates/ts/ai-bot/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/ai-bot/package.json.tpl b/templates/ts/ai-bot/package.json.tpl index ede25d7bb9..e5ecde723b 100644 --- a/templates/ts/ai-bot/package.json.tpl +++ b/templates/ts/ai-bot/package.json.tpl @@ -32,7 +32,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/ai-bot/teamsapp.testtool.yml b/templates/ts/ai-bot/teamsapp.testtool.yml index 3c7475a362..dc6c694554 100644 --- a/templates/ts/ai-bot/teamsapp.testtool.yml +++ b/templates/ts/ai-bot/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/api-plugin-from-scratch/README.md b/templates/ts/api-plugin-from-scratch/README.md index 6910063aad..ab0fd8597e 100644 --- a/templates/ts/api-plugin-from-scratch/README.md +++ b/templates/ts/api-plugin-from-scratch/README.md @@ -20,7 +20,7 @@ This app template allows Microsoft Copilot for Microsoft 365 to interact directl 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. -3. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)` from the launch configuration dropdown. +3. Select `Debug in Copilot (Edge)` or `Debug in Copilot (Chrome)` from the launch configuration dropdown. 4. Send a message to Copilot to find a repair record. ## What's included in the template diff --git a/templates/ts/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl b/templates/ts/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl index fb11edba12..c406a2a01e 100644 --- a/templates/ts/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl +++ b/templates/ts/api-plugin-from-scratch/appPackage/ai-plugin.json.tpl @@ -17,20 +17,7 @@ }, "required": [ "assignedTo" - ] - }, - "states": { - "reasoning": { - "description": "Returns the repair records.", - "instructions": [ - "Here are the parameters:", - " assignedTo: The person assigned to the repair." - ] - }, - "responding": { - "description": "Returns the repair result in JSON format.", - "instructions": "Extract and include as much relevant information as possible from the JSON result to meet the user's needs." - } + ] } } ], diff --git a/templates/ts/api-plugin-from-scratch/appPackage/manifest.json.tpl b/templates/ts/api-plugin-from-scratch/appPackage/manifest.json.tpl index 9ebc549abb..e345ae0488 100644 --- a/templates/ts/api-plugin-from-scratch/appPackage/manifest.json.tpl +++ b/templates/ts/api-plugin-from-scratch/appPackage/manifest.json.tpl @@ -25,7 +25,8 @@ "accentColor": "#FFFFFF", "plugins": [ { - "pluginFile": "ai-plugin.json" + "file": "ai-plugin.json", + "id": "plugin_1" } ], "permissions": [ diff --git a/templates/ts/api-plugin-from-scratch/teamsapp.local.yml.tpl b/templates/ts/api-plugin-from-scratch/teamsapp.local.yml.tpl index 09dfe6c0a4..15f1449729 100644 --- a/templates/ts/api-plugin-from-scratch/teamsapp.local.yml.tpl +++ b/templates/ts/api-plugin-from-scratch/teamsapp.local.yml.tpl @@ -21,12 +21,6 @@ provision: echo "::set-teamsfx-env FUNC_NAME=repair"; echo "::set-teamsfx-env FUNC_ENDPOINT=http://localhost:7071"; - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json - # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: @@ -35,12 +29,6 @@ provision: outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - # Apply the Teams app manifest to an existing Teams app in # Teams Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. diff --git a/templates/ts/api-plugin-from-scratch/teamsapp.yml.tpl b/templates/ts/api-plugin-from-scratch/teamsapp.yml.tpl index 7f1737126c..19c71d08df 100644 --- a/templates/ts/api-plugin-from-scratch/teamsapp.yml.tpl +++ b/templates/ts/api-plugin-from-scratch/teamsapp.yml.tpl @@ -42,12 +42,6 @@ provision: # will use bicep CLI in PATH if you remove this config. bicepCliVersion: v0.9.1 - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json - # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: @@ -56,12 +50,6 @@ provision: outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip - # Apply the Teams app manifest to an existing Teams app in # Teams Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. @@ -110,11 +98,6 @@ deploy: # Triggered when 'teamsapp publish' is executed publish: - # Validate using manifest schema - - uses: teamsApp/validateManifest - with: - # Path to manifest template - manifestPath: ./appPackage/manifest.json # Build Teams app package with latest env value - uses: teamsApp/zipAppPackage with: @@ -122,11 +105,6 @@ publish: manifestPath: ./appPackage/manifest.json outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json - # Validate app package using validation rules - - uses: teamsApp/validateAppPackage - with: - # Relative path to this file. This is the path for built zip file. - appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip # Apply the Teams app manifest to an existing Teams app in # Teams Developer Portal. # Will use the app id in manifest file to determine which Teams app to update. diff --git a/templates/ts/command-and-response/.appserviceignore b/templates/ts/command-and-response/.appserviceignore index 24f5be9bcf..81e4ebc4dd 100644 --- a/templates/ts/command-and-response/.appserviceignore +++ b/templates/ts/command-and-response/.appserviceignore @@ -25,3 +25,4 @@ teamsapp.*.yml /node_modules/typescript /appPackage/ /infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/command-and-response/.gitignore b/templates/ts/command-and-response/.gitignore index 01faebf252..dfb975ac86 100644 --- a/templates/ts/command-and-response/.gitignore +++ b/templates/ts/command-and-response/.gitignore @@ -21,3 +21,6 @@ lib/ .localConfigs .notification.localstore.json .notification.testtoolstore.json + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/ts/command-and-response/package.json.tpl b/templates/ts/command-and-response/package.json.tpl index d688d97f2b..5c30132e57 100644 --- a/templates/ts/command-and-response/package.json.tpl +++ b/templates/ts/command-and-response/package.json.tpl @@ -29,7 +29,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "nodemon": "^2.0.7", "ts-node": "^10.4.0", diff --git a/templates/ts/command-and-response/teamsapp.testtool.yml b/templates/ts/command-and-response/teamsapp.testtool.yml index bb912b9a9d..3217c43522 100644 --- a/templates/ts/command-and-response/teamsapp.testtool.yml +++ b/templates/ts/command-and-response/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/custom-copilot-assistant-assistants-api/.webappignore b/templates/ts/custom-copilot-assistant-assistants-api/.webappignore index 3e9dfd4d90..1759231814 100644 --- a/templates/ts/custom-copilot-assistant-assistants-api/.webappignore +++ b/templates/ts/custom-copilot-assistant-assistants-api/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-assistant-assistants-api/package.json.tpl b/templates/ts/custom-copilot-assistant-assistants-api/package.json.tpl index febe966f0b..4522c4ddc9 100644 --- a/templates/ts/custom-copilot-assistant-assistants-api/package.json.tpl +++ b/templates/ts/custom-copilot-assistant-assistants-api/package.json.tpl @@ -34,7 +34,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/custom-copilot-assistant-assistants-api/teamsapp.testtool.yml b/templates/ts/custom-copilot-assistant-assistants-api/teamsapp.testtool.yml index e116319a64..123d263045 100644 --- a/templates/ts/custom-copilot-assistant-assistants-api/teamsapp.testtool.yml +++ b/templates/ts/custom-copilot-assistant-assistants-api/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/custom-copilot-assistant-new/.gitignore b/templates/ts/custom-copilot-assistant-new/.gitignore index a0757d5341..a58569ebf7 100644 --- a/templates/ts/custom-copilot-assistant-new/.gitignore +++ b/templates/ts/custom-copilot-assistant-new/.gitignore @@ -17,4 +17,7 @@ node_modules/ .DS_Store # build -lib/ \ No newline at end of file +lib/ + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-assistant-new/.webappignore b/templates/ts/custom-copilot-assistant-new/.webappignore index 18a015a2a3..f79d01ac12 100644 --- a/templates/ts/custom-copilot-assistant-new/.webappignore +++ b/templates/ts/custom-copilot-assistant-new/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-assistant-new/env/.env.dev.user.tpl b/templates/ts/custom-copilot-assistant-new/env/.env.dev.user.tpl index 849946d3ca..ed67f2e2ac 100644 --- a/templates/ts/custom-copilot-assistant-new/env/.env.dev.user.tpl +++ b/templates/ts/custom-copilot-assistant-new/env/.env.dev.user.tpl @@ -23,5 +23,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-assistant-new/env/.env.local.user.tpl b/templates/ts/custom-copilot-assistant-new/env/.env.local.user.tpl index f021ef6e7b..b1c0fc39f2 100644 --- a/templates/ts/custom-copilot-assistant-new/env/.env.local.user.tpl +++ b/templates/ts/custom-copilot-assistant-new/env/.env.local.user.tpl @@ -24,5 +24,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-assistant-new/env/.env.testtool.user.tpl b/templates/ts/custom-copilot-assistant-new/env/.env.testtool.user.tpl index 9eec8f1cf9..8beb393d16 100644 --- a/templates/ts/custom-copilot-assistant-new/env/.env.testtool.user.tpl +++ b/templates/ts/custom-copilot-assistant-new/env/.env.testtool.user.tpl @@ -23,5 +23,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-assistant-new/infra/azure.bicep.tpl b/templates/ts/custom-copilot-assistant-new/infra/azure.bicep.tpl index 25e9a3461e..9a33563951 100644 --- a/templates/ts/custom-copilot-assistant-new/infra/azure.bicep.tpl +++ b/templates/ts/custom-copilot-assistant-new/infra/azure.bicep.tpl @@ -90,6 +90,10 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = { name: 'AZURE_OPENAI_ENDPOINT' value: azureOpenAIEndpoint } + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME' + value: azureOpenAIDeploymentName + } {{/useAzureOpenAI}} ] ftpsState: 'FtpsOnly' diff --git a/templates/ts/custom-copilot-assistant-new/package.json.tpl b/templates/ts/custom-copilot-assistant-new/package.json.tpl index 20608e7df8..d643e4dda6 100644 --- a/templates/ts/custom-copilot-assistant-new/package.json.tpl +++ b/templates/ts/custom-copilot-assistant-new/package.json.tpl @@ -33,7 +33,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/custom-copilot-assistant-new/teamsapp.testtool.yml.tpl b/templates/ts/custom-copilot-assistant-new/teamsapp.testtool.yml.tpl index b520d3fff6..52954e85ca 100644 --- a/templates/ts/custom-copilot-assistant-new/teamsapp.testtool.yml.tpl +++ b/templates/ts/custom-copilot-assistant-new/teamsapp.testtool.yml.tpl @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/custom-copilot-basic/.gitignore b/templates/ts/custom-copilot-basic/.gitignore index a0757d5341..a58569ebf7 100644 --- a/templates/ts/custom-copilot-basic/.gitignore +++ b/templates/ts/custom-copilot-basic/.gitignore @@ -17,4 +17,7 @@ node_modules/ .DS_Store # build -lib/ \ No newline at end of file +lib/ + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-basic/.webappignore b/templates/ts/custom-copilot-basic/.webappignore index 18a015a2a3..f79d01ac12 100644 --- a/templates/ts/custom-copilot-basic/.webappignore +++ b/templates/ts/custom-copilot-basic/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-basic/env/.env.dev.user.tpl b/templates/ts/custom-copilot-basic/env/.env.dev.user.tpl index 849946d3ca..ed67f2e2ac 100644 --- a/templates/ts/custom-copilot-basic/env/.env.dev.user.tpl +++ b/templates/ts/custom-copilot-basic/env/.env.dev.user.tpl @@ -23,5 +23,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-basic/env/.env.local.user.tpl b/templates/ts/custom-copilot-basic/env/.env.local.user.tpl index f021ef6e7b..b1c0fc39f2 100644 --- a/templates/ts/custom-copilot-basic/env/.env.local.user.tpl +++ b/templates/ts/custom-copilot-basic/env/.env.local.user.tpl @@ -24,5 +24,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-basic/env/.env.testtool.user.tpl b/templates/ts/custom-copilot-basic/env/.env.testtool.user.tpl index 9eec8f1cf9..8beb393d16 100644 --- a/templates/ts/custom-copilot-basic/env/.env.testtool.user.tpl +++ b/templates/ts/custom-copilot-basic/env/.env.testtool.user.tpl @@ -23,5 +23,10 @@ AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT= {{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-basic/package.json.tpl b/templates/ts/custom-copilot-basic/package.json.tpl index 20608e7df8..d643e4dda6 100644 --- a/templates/ts/custom-copilot-basic/package.json.tpl +++ b/templates/ts/custom-copilot-basic/package.json.tpl @@ -33,7 +33,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/custom-copilot-basic/teamsapp.testtool.yml.tpl b/templates/ts/custom-copilot-basic/teamsapp.testtool.yml.tpl index b520d3fff6..52954e85ca 100644 --- a/templates/ts/custom-copilot-basic/teamsapp.testtool.yml.tpl +++ b/templates/ts/custom-copilot-basic/teamsapp.testtool.yml.tpl @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/.gitignore b/templates/ts/custom-copilot-rag-azure-ai-search/.gitignore new file mode 100644 index 0000000000..bc090d9176 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/.gitignore @@ -0,0 +1,22 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +.localConfigs +.localConfigs.testTool +.notification.localstore.json +.notification.testtoolstore.json +appPackage/build + +# dependencies +node_modules/ + +# misc +.env +.deployment +.DS_Store + +# build +lib/ + +# devTools +devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/.localConfigs.testTool.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/.localConfigs.testTool.tpl new file mode 100644 index 0000000000..1cd7e4b6d7 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/.localConfigs.testTool.tpl @@ -0,0 +1,15 @@ +# A gitignored place holder file for local runtime configurations +BOT_ID= +BOT_PASSWORD= +{{#useOpenAI}} +OPENAI_API_KEY= +{{/useOpenAI}} +{{#useAzureOpenAI}} +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME= +{{/useAzureOpenAI}} +AZURE_SEARCH_KEY= +AZURE_SEARCH_ENDPOINT= +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/.localConfigs.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/.localConfigs.tpl new file mode 100644 index 0000000000..dee531d6a4 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/.localConfigs.tpl @@ -0,0 +1,14 @@ +# A gitignored place holder file for local runtime configurations +BOT_ID= +BOT_PASSWORD= +{{#useOpenAI}} +OPENAI_API_KEY= +{{/useOpenAI}} +{{#useAzureOpenAI}} +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME= +{{/useAzureOpenAI}} +AZURE_SEARCH_KEY= +AZURE_SEARCH_ENDPOINT= \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/extensions.json b/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/extensions.json new file mode 100644 index 0000000000..1b70a39308 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension" + ] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/launch.json.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/launch.json.tpl new file mode 100644 index 0000000000..9e3b45ee1f --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/launch.json.tpl @@ -0,0 +1,122 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Attach to Local Service", + "type": "node", + "request": "attach", + "port": 9239, + "restart": true, + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Teams (Edge)", + "configurations": [ + "Launch App (Edge)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { +{{#enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Chrome)", + "configurations": [ + "Launch App (Chrome)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { +{{#enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} + "order": 2 + }, + "stopAll": true + }, + { + "name": "Debug in Test Tool (Preview)", + "configurations": [ + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App (Test Tool)", + "presentation": { +{{#enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} + "order": 1 + }, + "stopAll": true + } + ] +} diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/settings.json b/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/settings.json new file mode 100644 index 0000000000..0d3ba10b02 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/tasks.json b/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/tasks.json new file mode 100644 index 0000000000..1c3e241f27 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/.vscode/tasks.json @@ -0,0 +1,204 @@ +// This file is automatically generated by Teams Toolkit. +// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0. +// See https://aka.ms/teamsfx-tasks for details on how to customize each task. +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Teams App (Test Tool)", + "dependsOn": [ + "Validate prerequisites (Test Tool)", + "Deploy (Test Tool)", + "Start application (Test Tool)", + "Start Test Tool", + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites (Test Tool)", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239, // app inspector port for Node.js debugger + 56150, // test tool port + ] + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy (Test Tool)", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "testtool", + } + }, + { + "label": "Start application (Test Tool)", + "type": "shell", + "command": "npm run dev:teamsfx:testtool", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}", + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + }, + { + "label": "Start Test Tool", + "type": "shell", + "command": "npm run dev:teamsfx:launch-testtool", + "isBackground": true, + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin:${env:PATH}" + } + }, + "windows": { + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin;${env:PATH}" + } + } + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Listening on" + } + }, + "presentation": { + "panel": "dedicated", + "reveal": "silent" + } + }, + { + "label": "Start Teams App Locally", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Provision", + "Deploy", + "Start application" + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239 // app inspector port for Node.js debugger + ] + } + }, + { + // Start the local tunnel service to forward public URL to local port and inspect traffic. + // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. + "label": "Start local tunnel", + "type": "teamsfx", + "command": "debug-start-local-tunnel", + "args": { + "type": "dev-tunnel", + "ports": [ + { + "portNumber": 3978, + "protocol": "http", + "access": "public", + "writeToEnvironmentFile": { + "endpoint": "BOT_ENDPOINT", // output tunnel endpoint as BOT_ENDPOINT + "domain": "BOT_DOMAIN" // output tunnel domain as BOT_DOMAIN + } + } + ], + "env": "local" + }, + "isBackground": true, + "problemMatcher": "$teamsfx-local-tunnel-watch" + }, + { + // Create the debug resources. + // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args. + "label": "Provision", + "type": "teamsfx", + "command": "provision", + "args": { + "env": "local" + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "local" + } + }, + { + "label": "Start application", + "type": "shell", + "command": "npm run dev:teamsfx", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + } + ] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/.webappignore b/templates/ts/custom-copilot-rag-azure-ai-search/.webappignore new file mode 100644 index 0000000000..f79d01ac12 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/.webappignore @@ -0,0 +1,28 @@ +.webappignore +.fx +.deployment +.localConfigs.testTool +.localConfigs +.notification.localstore.json +.notification.testtoolstore.json +.vscode +*.js.map +*.ts.map +*.ts +.git* +.tsbuildinfo +CHANGELOG.md +readme.md +local.settings.json +test +tsconfig.json +.DS_Store +teamsapp.yml +teamsapp.*.yml +/env/ +/node_modules/.bin +/node_modules/ts-node +/node_modules/typescript +/appPackage/ +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/README.md.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/README.md.tpl new file mode 100644 index 0000000000..45752e647c --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/README.md.tpl @@ -0,0 +1,90 @@ +# Overview of the AI Search Bot template + +This app template is built on top of [Teams AI library](https://aka.ms/teams-ai-library). +It showcases how to build an basic RAG bot in Teams capable of chatting with users but with context provided by Azure AI Search data source. + +- [Overview of the AI Search Bot template](#overview-of-the-ai-search-bot-template) + - [Get started with the AI Search Bot template](#get-started-with-the-ai-search-bot-template) + - [What's included in the template](#whats-included-in-the-template) + - [Extend the AI Search Bot template with more AI capabilities](#extend-the-ai-search-bot-template-with-more-ai-capabilities) + - [Additional information and references](#additional-information-and-references) + +## Get started with the AI Search Bot template + +> **Prerequisites** +> +> To run the AI Search bot template in your local dev machine, you will need: +> +> - [Node.js](https://nodejs.org/), supported versions: 16, 18 +> - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +{{#useOpenAI}} +> - An account with [OpenAI](https://platform.openai.com/) and [Azure AI Search](https://azure.microsoft.com/en-us/products/ai-services/ai-search). +{{/useOpenAI}} +{{#useAzureOpenAI}} +> - Prepare your own [Azure OpenAI](https://aka.ms/oai/access) resource and [Azure AI Search](https://azure.microsoft.com/en-us/products/ai-services/ai-search). +{{/useAzureOpenAI}} + +1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#useOpenAI}} +1. In file *env/.env.testtool.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=`. And fill in your Azure AI search key `SECRET_AZURE_SEARCH_KEY=` and endpoint `AZURE_SEARCH_ENDPOINT=`. +{{/useOpenAI}} +{{#useAzureOpenAI}} +1. In file *env/.env.testtool.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_API_KEY=`, endpoint `AZURE_OPENAI_ENDPOINT=`, deployment name `AZURE_OPENAI_DEPLOYMENT_NAME=`, and embedding deployment name `AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=`. And fill in your Azure AI search key `SECRET_AZURE_SEARCH_KEY=` and endpoint `AZURE_SEARCH_ENDPOINT=`. +{{/useAzureOpenAI}} +1. Do `npm install` and `npm run indexer:create` to create the my documents index. Once you're done using the sample it's good practice to delete the index. You can do so with the `npm run indexer:delete` command. +1. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. Select `Debug in Test Tool (Preview)`. +1. You can send any message to get a response from the bot. + +**Congratulations**! You are running an application that can now interact with users in Teams App Test Tool: + +![AI Search Bot](https://github.com/OfficeDev/TeamsFx/assets/13211513/f56e7602-a5d3-436a-ae01-78546d61717d) + +## What's included in the template + +| Folder | Contents | +| - | - | +| `.vscode` | VSCode files for debugging | +| `appPackage` | Templates for the Teams application manifest | +| `env` | Environment files | +| `infra` | Templates for provisioning Azure resources | +| `src` | The source code for the application | + +The following files can be customized and demonstrate an example implementation to get you started. + +| File | Contents | +| - | - | +|`src/index.ts`| Sets up the bot app server.| +|`src/adapter.ts`| Sets up the bot adapter.| +|`src/config.ts`| Defines the environment variables.| +|`src/prompts/chat/skprompt.txt`| Defines the prompt.| +|`src/prompts/chat/config.json`| Configures the prompt.| +|`src/app/app.ts`| Handles business logics for the RAG bot.| +|`src/app/azureAISearchDataSource.ts`| Defines the Azure AI search data source.| +|`src/indexers/data/*.md`| Raw text data sources.| +|`src/indexers/utils.ts`| Basic index tools. | +|`src/indexers/setup.ts`| A script to create index and upload documents. | +|`src/indexers/delete.ts`| A script to delete index and documents. | + +The following are Teams Toolkit specific project files. You can [visit a complete guide on Github](https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview) to understand how Teams Toolkit works. + +| File | Contents | +| - | - | +|`teamsapp.yml`|This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | +|`teamsapp.local.yml`|This overrides `teamsapp.yml` with actions that enable local execution and debugging.| +|`teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool.| + +## Extend the AI Search bot template with more AI capabilities + +You can follow [Build a Basic AI Chatbot in Teams](https://aka.ms/teamsfx-basic-ai-chatbot) to extend the Basic AI Chatbot template with more AI capabilities, like: +- [Customize prompt](https://aka.ms/teamsfx-basic-ai-chatbot#customize-prompt) +- [Customize user input](https://aka.ms/teamsfx-basic-ai-chatbot#customize-user-input) +- [Customize conversation history](https://aka.ms/teamsfx-basic-ai-chatbot#customize-conversation-history) +- [Customize model type](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-type) +- [Customize model parameters](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-parameters) +- [Handle messages with image](https://aka.ms/teamsfx-basic-ai-chatbot#handle-messages-with-image) + +## Additional information and references +- [Teams AI library](https://aka.ms/teams-ai-library) +- [Teams Toolkit Documentations](https://docs.microsoft.com/microsoftteams/platform/toolkit/teams-toolkit-fundamentals) +- [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +- [Teams Toolkit Samples](https://github.com/OfficeDev/TeamsFx-Samples) \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/appPackage/color.png b/templates/ts/custom-copilot-rag-azure-ai-search/appPackage/color.png new file mode 100644 index 0000000000..2d7e85c9e9 Binary files /dev/null and b/templates/ts/custom-copilot-rag-azure-ai-search/appPackage/color.png differ diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/appPackage/manifest.json.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/appPackage/manifest.json.tpl new file mode 100644 index 0000000000..d7a51bc8fb --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/appPackage/manifest.json.tpl @@ -0,0 +1,46 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", + "manifestVersion": "1.16", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.microsoft.teams.extension", + "developer": { + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/privacy", + "termsOfUseUrl": "https://www.example.com/termofuse" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "{{appName}}${{APP_NAME_SUFFIX}}", + "full": "full name for {{appName}}" + }, + "description": { + "short": "short description for {{appName}}", + "full": "full description for {{appName}}" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "${{BOT_ID}}", + "scopes": [ + "personal", + "team", + "groupchat" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "composeExtensions": [], + "configurableTabs": [], + "staticTabs": [], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/appPackage/outline.png b/templates/ts/custom-copilot-rag-azure-ai-search/appPackage/outline.png new file mode 100644 index 0000000000..245fa194db Binary files /dev/null and b/templates/ts/custom-copilot-rag-azure-ai-search/appPackage/outline.png differ diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.dev b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.dev new file mode 100644 index 0000000000..4b07861c03 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.dev @@ -0,0 +1,16 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.dev.user.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.dev.user.tpl new file mode 100644 index 0000000000..0a35e03f50 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.dev.user.tpl @@ -0,0 +1,50 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{#azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME='{{{azureOpenAIEmbeddingDeploymentName}}}' +{{/azureOpenAIEmbeddingDeploymentName}} +{{^azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME= +{{/azureOpenAIEmbeddingDeploymentName}} +{{/useAzureOpenAI}} +{{#secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY='{{{secretAzureSearchKey}}}' +{{/secretAzureSearchKey}} +{{^secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY= +{{/secretAzureSearchKey}} +{{#azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT='{{{azureSearchEndpoint}}}' +{{/azureSearchEndpoint}} +{{^azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT= +{{/azureSearchEndpoint}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.local b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.local new file mode 100644 index 0000000000..f3a75f8723 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.local @@ -0,0 +1,11 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=local +APP_NAME_SUFFIX=local + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_DOMAIN= +BOT_ENDPOINT= \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.local.user.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.local.user.tpl new file mode 100644 index 0000000000..13f3ff0b84 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.local.user.tpl @@ -0,0 +1,51 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{#azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME='{{{azureOpenAIEmbeddingDeploymentName}}}' +{{/azureOpenAIEmbeddingDeploymentName}} +{{^azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME= +{{/azureOpenAIEmbeddingDeploymentName}} +{{/useAzureOpenAI}} +{{#secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY='{{{secretAzureSearchKey}}}' +{{/secretAzureSearchKey}} +{{^secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY= +{{/secretAzureSearchKey}} +{{#azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT='{{{azureSearchEndpoint}}}' +{{/azureSearchEndpoint}} +{{^azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT= +{{/azureSearchEndpoint}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.testtool b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.testtool new file mode 100644 index 0000000000..43ce12aad3 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.testtool @@ -0,0 +1,8 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=testtool + +# Environment variables used by test tool +TEAMSAPPTESTER_PORT=56150 +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.testtool.user.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.testtool.user.tpl new file mode 100644 index 0000000000..744a906306 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/env/.env.testtool.user.tpl @@ -0,0 +1,50 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{#azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME='{{{azureOpenAIEmbeddingDeploymentName}}}' +{{/azureOpenAIEmbeddingDeploymentName}} +{{^azureOpenAIEmbeddingDeploymentName}} +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME= +{{/azureOpenAIEmbeddingDeploymentName}} +{{/useAzureOpenAI}} +{{#secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY='{{{secretAzureSearchKey}}}' +{{/secretAzureSearchKey}} +{{^secretAzureSearchKey}} +SECRET_AZURE_SEARCH_KEY= +{{/secretAzureSearchKey}} +{{#azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT='{{{azureSearchEndpoint}}}' +{{/azureSearchEndpoint}} +{{^azureSearchEndpoint}} +AZURE_SEARCH_ENDPOINT= +{{/azureSearchEndpoint}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/infra/azure.bicep.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/infra/azure.bicep.tpl new file mode 100644 index 0000000000..9cb0635756 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/infra/azure.bicep.tpl @@ -0,0 +1,138 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@description('Required when create Azure Bot service') +param botAadAppClientId string + +@secure() +@description('Required by Bot Framework package in your bot project') +param botAadAppClientSecret string + +{{#useOpenAI}} +@secure() +param openAIKey string +{{/useOpenAI}} +{{#useAzureOpenAI}} +@secure() +param azureOpenAIKey string + +@secure() +param azureOpenAIEndpoint string + +@secure() +param azureOpenAIDeploymentName string + +@secure() +param azureOpenAIEmbeddingDeploymentName string +{{/useAzureOpenAI}} + +@secure() +param azureSearchKey string + +@secure() +param azureSearchEndpoint string + +param webAppSKU string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param location string = resourceGroup().location + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + alwaysOn: true + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' // Run Azure App Service from a package file + } + { + name: 'WEBSITE_NODE_DEFAULT_VERSION' + value: '~18' // Set NodeJS version to 18.x for your site + } + { + name: 'RUNNING_ON_AZURE' + value: '1' + } + { + name: 'BOT_ID' + value: botAadAppClientId + } + { + name: 'BOT_PASSWORD' + value: botAadAppClientSecret + } + {{#useOpenAI}} + { + name: 'OPENAI_API_KEY' + value: openAIKey + } + {{/useOpenAI}} + {{#useAzureOpenAI}} + { + name: 'AZURE_OPENAI_API_KEY' + value: azureOpenAIKey + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: azureOpenAIEndpoint + } + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME' + value: azureOpenAIDeploymentName + } + { + name: 'AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME' + value: azureOpenAIEmbeddingDeploymentName + } + {{/useAzureOpenAI}} + { + name: 'AZURE_SEARCH_KEY' + value: azureSearchKey + } + { + name: 'AZURE_SEARCH_ENDPOINT' + value: azureSearchEndpoint + } + ] + ftpsState: 'FtpsOnly' + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + botAadAppClientId: botAadAppClientId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/infra/azure.parameters.json.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/infra/azure.parameters.json.tpl new file mode 100644 index 0000000000..47b691a8de --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/infra/azure.parameters.json.tpl @@ -0,0 +1,46 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "botAadAppClientId": { + "value": "${{BOT_ID}}" + }, + "botAadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + {{#useOpenAI}} + "openAIKey": { + "value": "${{SECRET_OPENAI_API_KEY}}" + }, + {{/useOpenAI}} + {{#useAzureOpenAI}} + "azureOpenAIKey": { + "value": "${{SECRET_AZURE_OPENAI_API_KEY}}" + }, + "azureOpenAIEndpoint": { + "value": "${{AZURE_OPENAI_ENDPOINT}}" + }, + "azureOpenAIDeploymentName": { + "value": "${{AZURE_OPENAI_DEPLOYMENT_NAME}}" + }, + "azureOpenAIEmbeddingDeploymentName": { + "value": "${{AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME}}" + }, + {{/useAzureOpenAI}} + "azureSearchKey": { + "value": "${{SECRET_AZURE_SEARCH_KEY}}" + }, + "azureSearchEndpoint": { + "value": "${{AZURE_SEARCH_ENDPOINT}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "{{appName}}" + } + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/infra/botRegistration/azurebot.bicep b/templates/ts/custom-copilot-rag-azure-ai-search/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..ab67c7a56b --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/infra/botRegistration/azurebot.bicep @@ -0,0 +1,37 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param botAadAppClientId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: botAadAppClientId + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/infra/botRegistration/readme.md b/templates/ts/custom-copilot-rag-azure-ai-search/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/package.json.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/package.json.tpl new file mode 100644 index 0000000000..e49f60335c --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/package.json.tpl @@ -0,0 +1,46 @@ +{ + "name": "{{SafeProjectNameLowerCase}}", + "version": "1.0.0", + "msteams": { + "teamsAppId": null + }, + "description": "Microsoft Teams Toolkit RAG Bot Sample with Azure AI Search and Teams AI Library", + "engines": { + "node": "16 || 18" + }, + "author": "Microsoft", + "license": "MIT", + "main": "./lib/src/index.js", + "scripts": { + "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", + "dev:teamsfx:testtool": "env-cmd --silent -f .localConfigs.testTool npm run dev", + "dev:teamsfx:launch-testtool": "env-cmd --silent -f env/.env.testtool teamsapptester start", + "dev": "nodemon --exec node --inspect=9239 --signal SIGINT -r ts-node/register ./src/index.ts", + "build": "tsc --build && shx cp -r ./src/prompts ./lib/src", + "start": "node ./lib/src/index.js", + "test": "echo \"Error: no test specified\" && exit 1", + "watch": "nodemon --exec \"npm run start\"", + "indexer:create": "npm run build && shx cp -r ./src/indexers/data ./lib/src/indexers && env-cmd --silent -f env/.env.testtool.user node ./lib/src/indexers/setup.js", + "indexer:delete": "npm run build && env-cmd --silent -f env/.env.testtool.user node ./lib/src/indexers/delete.js" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "dependencies": { + "@azure/search-documents": "^12.0.0", + "@microsoft/teams-ai": "^1.1.0", + "botbuilder": "^4.20.0", + "openai": "~4.28.4", + "restify": "^10.0.0" + }, + "devDependencies": { + "@types/restify": "^8.5.5", + "@types/node": "^14.0.0", + "env-cmd": "^10.1.0", + "ts-node": "^10.4.0", + "typescript": "^4.4.4", + "nodemon": "^2.0.7", + "shx": "^0.3.3" + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/adapter.ts b/templates/ts/custom-copilot-rag-azure-ai-search/src/adapter.ts new file mode 100644 index 0000000000..1cf10f4bb8 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/adapter.ts @@ -0,0 +1,51 @@ +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +import { + CloudAdapter, + ConfigurationBotFrameworkAuthentication, + ConfigurationServiceClientCredentialFactory, +} from "botbuilder"; + +// This bot's main dialog. +import config from "./config"; + +const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication( + {}, + new ConfigurationServiceClientCredentialFactory({ + MicrosoftAppId: config.botId, + MicrosoftAppPassword: process.env.BOT_PASSWORD, + MicrosoftAppType: "MultiTenant", + }) +); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about how bots work. +const adapter = new CloudAdapter(botFrameworkAuthentication); + +// Catch-all for errors. +const onTurnErrorHandler = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. + console.error(`\n [onTurnError] unhandled error: ${error}`); + + // Only send error message for user messages, not for other message types so the bot doesn't spam a channel or chat. + if (context.activity.type === "message") { + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendTraceActivity( + "OnTurnError Trace", + `${error}`, + "https://www.botframework.com/schemas/error", + "TurnError" + ); + + // Send a message to the user + await context.sendActivity("The bot encountered an error or bug."); + await context.sendActivity("To continue to run this bot, please fix the bot source code."); + } +}; + +// Set the onTurnError for the singleton CloudAdapter. +adapter.onTurnError = onTurnErrorHandler; + +export default adapter; diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/app/app.ts.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/src/app/app.ts.tpl new file mode 100644 index 0000000000..66177c09eb --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/app/app.ts.tpl @@ -0,0 +1,61 @@ +import { MemoryStorage } from "botbuilder"; +import * as path from "path"; +import config from "../config"; + +// See https://aka.ms/teams-ai-library to learn more about the Teams AI library. +import { Application, ActionPlanner, OpenAIModel, PromptManager, TurnState } from "@microsoft/teams-ai"; +import { AzureAISearchDataSource } from "./azureAISearchDataSource"; + +// Create AI components +const model = new OpenAIModel({ + {{#useOpenAI}} + apiKey: config.openAIKey, + defaultModel: config.openAIModelName, + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureApiKey: config.azureOpenAIKey, + azureDefaultDeployment: config.azureOpenAIDeploymentName, + azureEndpoint: config.azureOpenAIEndpoint, + {{/useAzureOpenAI}} + + useSystemMessages: true, + logRequests: true, +}); +const prompts = new PromptManager({ + promptsFolder: path.join(__dirname, "../prompts"), +}); +const planner = new ActionPlanner({ + model, + prompts, + defaultPrompt: "chat", +}); + +// Register your data source with planner +planner.prompts.addDataSource( + new AzureAISearchDataSource({ + name: "azure-ai-search", + indexName: "my-documents", + azureAISearchApiKey: config.azureSearchKey!, + azureAISearchEndpoint: config.azureSearchEndpoint!, + {{#useOpenAI}} + apiKey: config.openAIKey!, + openAIEmbeddingModelName: config.openAIEmbeddingModelName!, + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureOpenAIApiKey: config.azureOpenAIKey!, + azureOpenAIEndpoint: config.azureOpenAIEndpoint!, + azureOpenAIEmbeddingDeploymentName: config.azureOpenAIEmbeddingDeploymentName!, + {{/useAzureOpenAI}} + }) +); + +// Define storage and application +const storage = new MemoryStorage(); +const app = new Application({ + storage, + ai: { + planner, + }, +}); + +export default app; diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/app/azureAISearchDataSource.ts.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/src/app/azureAISearchDataSource.ts.tpl new file mode 100644 index 0000000000..83126b97ee --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/app/azureAISearchDataSource.ts.tpl @@ -0,0 +1,202 @@ +import { DataSource, Memory, OpenAIEmbeddings, RenderedPromptSection, Tokenizer } from "@microsoft/teams-ai"; +import { TurnContext } from "botbuilder"; +import { AzureKeyCredential, SearchClient } from "@azure/search-documents"; + +/** + * Defines the Document Interface. + */ +export interface MyDocument { + docId?: string; + docTitle?: string | null; + description?: string | null; + descriptionVector?: number[] | null; +} + +/** + * Options for creating a `AzureAISearchDataSource`. + */ +export interface AzureAISearchDataSourceOptions { + /** + * Name of the data source. This is the name that will be used to reference the data source in the prompt template. + */ + name: string; + + /** + * Name of the Azure AI Search index. + */ + indexName: string; + + {{#useOpenAI}} + /** + * OpenAI API key. + */ + apiKey: string; + /** + * OpenAI model to use for generating embeddings. + */ + openAIEmbeddingModelName: string; + {{/useOpenAI}} + {{#useAzureOpenAI}} + /** + * Azure OpenAI API key. + */ + azureOpenAIApiKey: string; + + /** + * Azure OpenAI endpoint. This is used to generate embeddings for the user's input. + */ + azureOpenAIEndpoint: string; + + /** + * Azure OpenAI Embedding deployment. This is used to generate embeddings for the user's input. + */ + azureOpenAIEmbeddingDeploymentName: string; + {{/useAzureOpenAI}} + + /** + * Azure AI Search API key. + */ + azureAISearchApiKey: string; + + /** + * Azure AI Search endpoint. + */ + azureAISearchEndpoint: string; +} + +/** + * A data source that searches through Azure AI search. + */ +export class AzureAISearchDataSource implements DataSource { + /** + * Name of the data source. + */ + public readonly name: string; + + /** + * Options for creating the data source. + */ + private readonly options: AzureAISearchDataSourceOptions; + + /** + * Azure AI Search client. + */ + private readonly searchClient: SearchClient; + + /** + * Creates a new `AzureAISearchDataSource` instance. + * @param {AzureAISearchDataSourceOptions} options Options for creating the data source. + */ + public constructor(options: AzureAISearchDataSourceOptions) { + this.name = options.name; + this.options = options; + this.searchClient = new SearchClient( + options.azureAISearchEndpoint, + options.indexName, + new AzureKeyCredential(options.azureAISearchApiKey), + {} + ); + } + + /** + * Renders the data source as a string of text. + * @remarks + * The returned output should be a string of text that will be injected into the prompt at render time. + * @param context Turn context for the current turn of conversation with the user. + * @param memory An interface for accessing state values. + * @param tokenizer Tokenizer to use when rendering the data source. + * @param maxTokens Maximum number of tokens allowed to be rendered. + * @returns A promise that resolves to the rendered data source. + */ + public async renderData(context: TurnContext, memory: Memory, tokenizer: Tokenizer, maxTokens: number): Promise> { + const query = memory.getValue("temp.input") as string; + if(!query) { + return { output: "", length: 0, tooLong: false }; + } + + const selectedFields = [ + "docId", + "docTitle", + "description", + ]; + + // hybrid search + const queryVector: number[] = await this.getEmbeddingVector(query); + const searchResults = await this.searchClient.search(query, { + searchFields: ["docTitle", "description"], + select: selectedFields as any, + vectorSearchOptions: { + queries: [ + { + kind: "vector", + fields: ["descriptionVector"], + kNearestNeighborsCount: 2, + // The query vector is the embedding of the user's input + vector: queryVector + } + ] + }, + }); + + if (!searchResults.results) { + return { output: "", length: 0, tooLong: false }; + } + + // Concatenate the documents string into a single document + // until the maximum token limit is reached. This can be specified in the prompt template. + let usedTokens = 0; + let doc = ""; + for await (const result of searchResults.results) { + const formattedResult = this.formatDocument(result.document.description); + const tokens = tokenizer.encode(formattedResult).length; + + if (usedTokens + tokens > maxTokens) { + break; + } + + doc += formattedResult; + usedTokens += tokens; + } + + return { output: doc, length: usedTokens, tooLong: usedTokens > maxTokens }; + } + + /** + * Formats the result string + * @param result + * @returns + */ + private formatDocument(result: string): string { + return `${result}`; + } + + /** + * Generate embeddings for the user's input. + * @param {string} text - The user's input. + * @returns {Promise} The embedding vector for the user's input. + */ + private async getEmbeddingVector(text: string): Promise { + {{#useOpenAI}} + const embeddings = new OpenAIEmbeddings({ + apiKey: this.options.apiKey, + model: this.options.openAIEmbeddingModelName, + }); + const result = await embeddings.createEmbeddings(this.options.openAIEmbeddingModelName, text); + {{/useOpenAI}} + {{#useAzureOpenAI}} + const embeddings = new OpenAIEmbeddings({ + azureApiKey: this.options.azureOpenAIApiKey, + azureEndpoint: this.options.azureOpenAIEndpoint, + azureDeployment: this.options.azureOpenAIEmbeddingDeploymentName, + }); + + const result = await embeddings.createEmbeddings(this.options.azureOpenAIEmbeddingDeploymentName, text); + {{/useAzureOpenAI}} + + if (result.status !== "success" || !result.output) { + throw new Error(`Failed to generate embeddings for description: ${text}`); + } + + return result.output[0]; + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/config.ts.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/src/config.ts.tpl new file mode 100644 index 0000000000..25c43ffab9 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/config.ts.tpl @@ -0,0 +1,19 @@ +const config = { + botId: process.env.BOT_ID, + botPassword: process.env.BOT_PASSWORD, + {{#useOpenAI}} + openAIKey: process.env.OPENAI_API_KEY, + openAIModelName: "gpt-3.5-turbo", + openAIEmbeddingModelName: "text-embedding-ada-002", + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureOpenAIKey: process.env.AZURE_OPENAI_API_KEY, + azureOpenAIEndpoint: process.env.AZURE_OPENAI_ENDPOINT, + azureOpenAIDeploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME, + azureOpenAIEmbeddingDeploymentName: process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME, + {{/useAzureOpenAI}} + azureSearchKey: process.env.AZURE_SEARCH_KEY, + azureSearchEndpoint: process.env.AZURE_SEARCH_ENDPOINT, +}; + +export default config; diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/index.ts b/templates/ts/custom-copilot-rag-azure-ai-search/src/index.ts new file mode 100644 index 0000000000..0db519818e --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/index.ts @@ -0,0 +1,25 @@ +// Import required packages +import * as restify from "restify"; + +// This bot's adapter +import adapter from "./adapter"; + +// This bot's main dialog. +import app from "./app/app"; + +// Create HTTP server. +const server = restify.createServer(); +server.use(restify.plugins.bodyParser()); + +server.listen(process.env.port || process.env.PORT || 3978, () => { + console.log(`\nBot Started, ${server.name} listening to ${server.url}`); +}); + +// Listen for incoming server requests. +server.post("/api/messages", async (req, res) => { + // Route received a request to adapter for processing + await adapter.process(req, res as any, async (context) => { + // Dispatch to application for routing + await app.run(context); + }); +}); diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso Electronics_PerkPlus_Program.md b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso Electronics_PerkPlus_Program.md new file mode 100644 index 0000000000..1d97d5117e --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso Electronics_PerkPlus_Program.md @@ -0,0 +1,36 @@ +# Contoso Electronics PerksPlus Program + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Overview +Introducing PerksPlus - the ultimate benefits program designed to support the health and wellness of employees. With PerksPlus, employees have the opportunity to expense up to $1000 for fitness-related programs, making it easier and more affordable to maintain a healthy lifestyle. PerksPlus is not only designed to support employees' physical health, but also their mental health. Regular exercise has been shown to reduce stress, improve mood, and enhance overall well-being. With PerksPlus, employees can invest in their health and wellness, while enjoying the peace of mind that comes with knowing they are getting the support they need to lead a healthy life. +What is Covered? + +PerksPlus covers a wide range of fitness activities, including but not limited to: +* Gym memberships +* Personal training sessions +* Yoga and Pilates classes +* Fitness equipment purchases +* Sports team fees +* Health retreats and spas +* Outdoor adventure activities (such as rock climbing, hiking, and kayaking) +* Group fitness classes (such as dance, martial arts, and cycling) +* Virtual fitness programs (such as online yoga and workout classes) + +In addition to the wide range of fitness activities covered by PerksPlus, the program also covers a variety of lessons and experiences that promote health and wellness. Some of the lessons covered under PerksPlus include: +* Skiing and snowboarding lessons +* Scuba diving lessons +* Surfing lessons +* Horseback riding lessons + +These lessons provide employees with the opportunity to try new things, challenge themselves, and improve their physical skills. They are also a great way to relieve stress and have fun while staying active. + +With PerksPlus, employees can choose from a variety of fitness programs to suit their individual needs and preferences. Whether you're looking to improve your physical fitness, reduce stress, or just have some fun, PerksPlus has you covered. + +## What is Not Covered? +In addition to the wide range of activities covered by PerksPlus, there is also a list of things that are not +covered under the program. These include but are not limited to: +* Non-fitness related expenses +* Medical treatments and procedures +* Travel expenses (unless related to a fitness program) +* Food and supplements \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Company_Overview.md b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Company_Overview.md new file mode 100644 index 0000000000..6878a8e204 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Company_Overview.md @@ -0,0 +1,48 @@ +# Contoso Electronics Company Overview + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## History + +Contoso Electronics, a pioneering force in the tech industry, was founded in 1985 by visionary entrepreneurs with a passion for innovation. Over the years, the company has played a pivotal role in shaping the landscape of consumer electronics. + +| Year | Milestone | +|------|-----------| +| 1985 | Company founded with a focus on cutting-edge technology | +| 1990 | Launched the first-ever handheld personal computer | +| 2000 | Introduced groundbreaking advancements in AI and robotics | +| 2015 | Expansion into sustainable and eco-friendly product lines | + +## Company Overview + +At Contoso Electronics, we take pride in fostering a dynamic and inclusive workplace. Our dedicated team of experts collaborates to create innovative solutions that empower and connect people globally. + +### Core Values + +- **Innovation:** Constantly pushing the boundaries of technology. +- **Diversity:** Embracing different perspectives for creative excellence. +- **Sustainability:** Committed to eco-friendly practices in our products. + +## Vacation Perks + +We believe in work-life balance and understand the importance of well-deserved breaks. Our vacation perks are designed to help our employees recharge and return with renewed enthusiasm. + +| Vacation Tier | Duration | Additional Benefits | +|---------------|----------|---------------------| +| Standard | 2 weeks | Health and wellness stipend | +| Senior | 4 weeks | Travel vouchers for a dream destination | +| Executive | 6 weeks | Luxury resort getaway with family | + +## Employee Recognition + +Recognizing the hard work and dedication of our employees is at the core of our culture. Here are some ways we celebrate achievements: + +- Monthly "Innovator of the Month" awards +- Annual gala with awards for outstanding contributions +- Team-building retreats for high-performing departments + +## Join Us! + +Contoso Electronics is always on the lookout for talented individuals who share our passion for innovation. If you're ready to be part of a dynamic team shaping the future of technology, check out our [careers page](http://www.contoso.com) for exciting opportunities. + +[Learn more about Contoso Electronics!](http://www.contoso.com) diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Plan_Benefits.md b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Plan_Benefits.md new file mode 100644 index 0000000000..9da5c6429d --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/data/Contoso_Electronics_Plan_Benefits.md @@ -0,0 +1,37 @@ +# Contoso Electronics Plan and Benefit Packages + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Northwind Health Plus + +Northwind Health Plus is a comprehensive plan that provides comprehensive coverage for medical, vision, and dental services. This plan also offers prescription drug coverage, mental health and substance abuse coverage, and coverage for preventive care services. With Northwind Health Plus, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. + +This plan also offers coverage for emergency services, both in-network and out-of-network. + +## Northwind Standard + +Northwind Standard is a basic plan that provides coverage for medical, vision, and dental services. This plan also offers coverage for preventive care services, as well as prescription drug coverage. With Northwind Standard, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. This plan does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +## Comparison of Plans + +Both plans offer coverage for routine physicals, well-child visits, immunizations, and other preventive care services. The plans also cover preventive care services such as mammograms, colonoscopies, and other cancer screenings. + +Northwind Health Plus offers more comprehensive coverage than Northwind Standard. This plan offers coverage for emergency services, both in-network and out-of-network, as well as mental health and substance abuse coverage. Northwind Standard does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +Both plans offer coverage for prescription drugs. Northwind Health Plus offers a wider range of prescription drug coverage than Northwind Standard. Northwind Health Plus covers generic, brand-name, and specialty drugs, while Northwind Standard only covers generic and brand-name drugs. + +Both plans offer coverage for vision and dental services. Northwind Health Plus offers coverage for vision exams, glasses, and contact lenses, as well as dental exams, cleanings, and fillings. Northwind Standard only offers coverage for vision exams and glasses. + +Both plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays, doctor visits, lab tests, and X-rays. Northwind Standard only offers coverage for doctor visits and lab tests. + +Northwind Health Plus is a comprehensive plan that offers more coverage than Northwind Standard. Northwind Health Plus offers coverage for emergency services, mental health and substance abuse coverage, and out-of-network services, while Northwind Standard does not. Northwind Health Plus also offers a wider range of prescription drug coverage than Northwind Standard. Both plans offer coverage for vision and dental services, as well as medical services. + +## Cost Comparison + +Contoso Electronics deducts the employee's portion of the healthcare cost from each paycheck. This means that the cost of the health insurance will be spread out over the course of the year, rather than being paid in one lump sum. The employee's portion of the cost will be calculated based on the selected health plan and the number of people covered by the insurance. The table below shows a cost comparison between the different health plans offered by Contoso Electronics + +| | Northwind Standard | NorthWind Health Plus | +|---------------|----------|---------------------| +| Employee Only | $45.00 | $55.00 | +| Employee +1 | $65.00 | $71.00 | +| Employee +2 or more | $78.00 | $89.00 | \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/delete.ts b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/delete.ts new file mode 100644 index 0000000000..aa88ef6555 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/delete.ts @@ -0,0 +1,10 @@ +import { AzureKeyCredential, SearchIndexClient } from "@azure/search-documents"; +import { deleteIndex } from "./utils"; + +const index = "my-documents"; +const searchApiKey = process.env.SECRET_AZURE_SEARCH_KEY!; +const searchApiEndpoint = process.env.AZURE_SEARCH_ENDPOINT!; +const credentials = new AzureKeyCredential(searchApiKey); + +const searchIndexClient = new SearchIndexClient(searchApiEndpoint, credentials); +deleteIndex(searchIndexClient, index); diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/setup.ts.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/setup.ts.tpl new file mode 100644 index 0000000000..ba7db5db20 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/setup.ts.tpl @@ -0,0 +1,64 @@ +import { AzureKeyCredential, SearchClient, SearchIndexClient } from "@azure/search-documents"; +import { createIndexIfNotExists, delay, upsertDocuments, getEmbeddingVector } from "./utils"; +import { MyDocument } from "../app/azureAISearchDataSource"; +import path from "path"; +import * as fs from "fs"; + +/** + * Main function that creates the index and upserts the documents. + */ +export async function main() { + const index = "my-documents"; + + if ( + !process.env.SECRET_AZURE_SEARCH_KEY || + !process.env.AZURE_SEARCH_ENDPOINT || + {{#useOpenAI}} + !process.env.SECRET_OPENAI_API_KEY + {{/useOpenAI}} + {{#useAzureOpenAI}} + !process.env.SECRET_AZURE_OPENAI_API_KEY || + !process.env.AZURE_OPENAI_ENDPOINT || + !process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME + {{/useAzureOpenAI}} + ) { + {{#useOpenAI}} + throw new Error( + "Missing environment variables - please check that SECRET_AZURE_SEARCH_KEY, AZURE_SEARCH_ENDPOINT and SECRET_OPENAI_API_KEY are set." + ); + {{/useOpenAI}} + {{#useAzureOpenAI}} + throw new Error( + "Missing environment variables - please check that SECRET_AZURE_SEARCH_KEY, AZURE_SEARCH_ENDPOINT, SECRET_AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME are set." + ); + {{/useAzureOpenAI}} + } + + const searchApiKey = process.env.SECRET_AZURE_SEARCH_KEY!; + const searchApiEndpoint = process.env.AZURE_SEARCH_ENDPOINT!; + const credentials = new AzureKeyCredential(searchApiKey); + + const searchIndexClient = new SearchIndexClient(searchApiEndpoint, credentials); + createIndexIfNotExists(searchIndexClient, index); + // Wait 5 seconds for the index to be created + await delay(5000); + + const searchClient = new SearchClient(searchApiEndpoint, index, credentials); + + const filePath = path.join(__dirname, "./data"); + const files = fs.readdirSync(filePath); + const data: MyDocument[] = []; + for (let i=1;i<=files.length;i++) { + const content = fs.readFileSync(path.join(filePath, files[i-1]), "utf-8"); + data.push({ + docId: i+"", + docTitle: files[i-1], + description: content, + descriptionVector: await getEmbeddingVector(content), + }); + } + await upsertDocuments(searchClient, data); +} + +main(); + diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/utils.ts.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/utils.ts.tpl new file mode 100644 index 0000000000..e9bc58b8cc --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/indexers/utils.ts.tpl @@ -0,0 +1,132 @@ +/** + * Defines the utility methods. + */ +import { + SearchIndexClient, + SearchIndex, + KnownAnalyzerNames, + SearchClient, + IndexDocumentsResult +} from "@azure/search-documents"; +import { MyDocument } from "../app/azureAISearchDataSource"; +import { OpenAIEmbeddings } from "@microsoft/teams-ai"; +{{#useOpenAI}} +import config from "../config"; +{{/useOpenAI}} + +/** + * A wrapper for setTimeout that resolves a promise after timeInMs milliseconds. + * @param {number} timeInMs - The number of milliseconds to be delayed. + * @returns {Promise} Promise that is resolved after timeInMs + */ +export function delay(timeInMs: number): Promise { + return new Promise((resolve) => setTimeout(resolve, timeInMs)); +} + +/** + * Deletes the index with the given name + * @param {SearchIndexClient} client - The search index client + * @param {string} name - The name of the index + * @returns {Promise} A promise that resolves when the index is deleted + */ +export function deleteIndex(client: SearchIndexClient, name: string): Promise { + return client.deleteIndex(name); +} + +/** + * Adds or updates the given documents in the index + * @param {SearchClient} client - The search index client + * @param {Restaurant[]} documents - The documents to be added or updated + * @returns {Promise} The result of the operation + */ +export async function upsertDocuments( + client: SearchClient, + documents: MyDocument[] +): Promise { + return await client.mergeOrUploadDocuments(documents); +} + +/** + * Creates the index with the given name + * @param {SearchIndexClient} client - The search index client + * @param {string} name - The name of the index + */ +export async function createIndexIfNotExists(client: SearchIndexClient, name: string): Promise { + const MyDocumentIndex: SearchIndex = { + name, + fields: [ + { + type: "Edm.String", + name: "docId", + key: true, + filterable: true, + sortable: true + }, + { + type: "Edm.String", + name: "docTitle", + searchable: true, + filterable: true, + sortable: true + }, + { + type: "Edm.String", + name: "description", + searchable: true, + analyzerName: KnownAnalyzerNames.EnLucene + }, + { + type: "Collection(Edm.Single)", + name: "descriptionVector", + searchable: true, + vectorSearchDimensions: 1536, + vectorSearchProfileName: "my-vector-config" + }, + ], + corsOptions: { + // for browser tests + allowedOrigins: ["*"] + }, + vectorSearch: { + algorithms: [{ name: "vector-search-algorithm", kind: "hnsw" }], + profiles: [ + { + name: "my-vector-config", + algorithmConfigurationName: "vector-search-algorithm" + } + ] + } + }; + + await client.createOrUpdateIndex(MyDocumentIndex); +} + +/** + * + * @param {string} text - The text for which to generate the embedding vector. + * @returns {Promise} A promise that resolves to the embedding vector. + */ +export async function getEmbeddingVector(text: string): Promise { + {{#useOpenAI}} + const embeddings = new OpenAIEmbeddings({ + apiKey: process.env.SECRET_OPENAI_API_KEY!, + model: config.openAIEmbeddingModelName + }); + const result = await embeddings.createEmbeddings(config.openAIEmbeddingModelName, text); + {{/useOpenAI}} + {{#useAzureOpenAI}} + const embeddings = new OpenAIEmbeddings({ + azureApiKey: process.env.SECRET_AZURE_OPENAI_API_KEY!, + azureEndpoint: process.env.AZURE_OPENAI_ENDPOINT!, + azureDeployment: process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME!, + }); + + const result = await embeddings.createEmbeddings( process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME!, text); + {{/useAzureOpenAI}} + + if (result.status !== "success" || !result.output) { + throw new Error(`Failed to generate embeddings for description: ${text}`); + } + + return result.output[0]; +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/prompts/chat/config.json b/templates/ts/custom-copilot-rag-azure-ai-search/src/prompts/chat/config.json new file mode 100644 index 0000000000..4367c3fc5c --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/prompts/chat/config.json @@ -0,0 +1,22 @@ +{ + "schema": 1.1, + "description": "Chat with Teams RAG.", + "type": "completion", + "completion": { + "completion_type": "chat", + "include_history": true, + "include_input": true, + "max_input_tokens": 2800, + "max_tokens": 1000, + "temperature": 0.9, + "top_p": 0.0, + "presence_penalty": 0.6, + "frequency_penalty": 0.0, + "stop_sequences": [] + }, + "augmentation": { + "data_sources": { + "azure-ai-search": 1200 + } + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/src/prompts/chat/skprompt.txt b/templates/ts/custom-copilot-rag-azure-ai-search/src/prompts/chat/skprompt.txt new file mode 100644 index 0000000000..2a2ebee5a3 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/src/prompts/chat/skprompt.txt @@ -0,0 +1,3 @@ +The following is a conversation with an AI assistant, who is an expert on answering questions over the given context. +Responses should be in a short journalistic style with no more than 80 words. +Use the context provided in the `` tags as the source for your answers. \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/teamsapp.local.yml.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/teamsapp.local.yml.tpl new file mode 100644 index 0000000000..4abd73f918 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/teamsapp.local.yml.tpl @@ -0,0 +1,93 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create + with: + botId: ${{BOT_ID}} + name: {{appName}} + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install --no-audit + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs + envs: + BOT_ID: ${{BOT_ID}} + BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + {{#useOpenAI}} + OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME}} + {{/useAzureOpenAI}} + AZURE_SEARCH_KEY: ${{SECRET_AZURE_SEARCH_KEY}} + AZURE_SEARCH_ENDPOINT: ${{AZURE_SEARCH_ENDPOINT}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/teamsapp.testtool.yml.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/teamsapp.testtool.yml.tpl new file mode 100644 index 0000000000..20c10e6511 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/teamsapp.testtool.yml.tpl @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.1-beta + symlinkDir: ./devTools/teamsapptester + + # Run npm command + - uses: cli/runNpmCommand + with: + args: install --no-audit + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs.testTool + envs: + {{#useOpenAI}} + OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME}} + {{/useAzureOpenAI}} + AZURE_SEARCH_KEY: ${{SECRET_AZURE_SEARCH_KEY}} + AZURE_SEARCH_ENDPOINT: ${{AZURE_SEARCH_ENDPOINT}} + TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/teamsapp.yml.tpl b/templates/ts/custom-copilot-rag-azure-ai-search/teamsapp.yml.tpl new file mode 100644 index 0000000000..80699e1edb --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/teamsapp.yml.tpl @@ -0,0 +1,145 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Teams Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsapp deploy' is executed +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install + - uses: cli/runNpmCommand + name: build app + with: + args: run build --if-present + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: . + # Ignore file location, leave blank will ignore nothing + ignoreFile: .webappignore + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + +# Triggered when 'teamsapp publish' is executed +publish: + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Publish the app to + # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps) + # for review and approval + - uses: teamsApp/publishAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + publishedAppId: TEAMS_APP_PUBLISHED_APP_ID diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/tsconfig.json b/templates/ts/custom-copilot-rag-azure-ai-search/tsconfig.json new file mode 100644 index 0000000000..a68afb21f7 --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "declaration": true, + "target": "es2017", + "module": "commonjs", + "outDir": "./lib", + "rootDir": "./", + "sourceMap": true, + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "esModuleInterop": true + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-azure-ai-search/web.config b/templates/ts/custom-copilot-rag-azure-ai-search/web.config new file mode 100644 index 0000000000..793a3a982b --- /dev/null +++ b/templates/ts/custom-copilot-rag-azure-ai-search/web.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-custom-api/.gitignore b/templates/ts/custom-copilot-rag-custom-api/.gitignore index a0757d5341..a58569ebf7 100644 --- a/templates/ts/custom-copilot-rag-custom-api/.gitignore +++ b/templates/ts/custom-copilot-rag-custom-api/.gitignore @@ -17,4 +17,7 @@ node_modules/ .DS_Store # build -lib/ \ No newline at end of file +lib/ + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-custom-api/.webappignore b/templates/ts/custom-copilot-rag-custom-api/.webappignore index 18a015a2a3..f79d01ac12 100644 --- a/templates/ts/custom-copilot-rag-custom-api/.webappignore +++ b/templates/ts/custom-copilot-rag-custom-api/.webappignore @@ -24,4 +24,5 @@ teamsapp.*.yml /node_modules/ts-node /node_modules/typescript /appPackage/ -/infra/ \ No newline at end of file +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-custom-api/README.md.tpl b/templates/ts/custom-copilot-rag-custom-api/README.md.tpl index b0c9a92d85..7393f89f58 100644 --- a/templates/ts/custom-copilot-rag-custom-api/README.md.tpl +++ b/templates/ts/custom-copilot-rag-custom-api/README.md.tpl @@ -34,7 +34,7 @@ The app template is built using the Teams AI library, which provides the capabil 1. In file *env/.env.testtool.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=`. {{/useOpenAI}} {{#useAzureOpenAI}} -1. In file *env/.env.testtool.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_ENDPOINT=`, endpoint `SECRET_AZURE_OPENAI_ENDPOINT=` and deployment name `AZURE_OPENAI_DEPLOYMENT=`. +1. In file *env/.env.testtool.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_ENDPOINT=`, endpoint `SECRET_AZURE_OPENAI_ENDPOINT=` and deployment name `AZURE_OPENAI_MODEL_DEPLOYMENT_NAME=`. {{/useAzureOpenAI}} 1. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. Select `Debug in Test Tool (Preview)`. 1. You can send any message to get a response from the bot. @@ -49,7 +49,7 @@ The app template is built using the Teams AI library, which provides the capabil 1. In file *env/.env.local.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=`. {{/useOpenAI}} {{#useAzureOpenAI}} -1. In file *env/.env.local.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_ENDPOINT=`, endpoint `SECRET_AZURE_OPENAI_ENDPOINT= and deployment name `AZURE_OPENAI_DEPLOYMENT=`. +1. In file *env/.env.local.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_ENDPOINT=`, endpoint `SECRET_AZURE_OPENAI_ENDPOINT= and deployment name `AZURE_OPENAI_MODEL_DEPLOYMENT_NAME=`. {{/useAzureOpenAI}} 1. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. 1. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams. diff --git a/templates/ts/custom-copilot-rag-custom-api/env/.env.dev.user.tpl b/templates/ts/custom-copilot-rag-custom-api/env/.env.dev.user.tpl index f0f2496a45..0cf7fab130 100644 --- a/templates/ts/custom-copilot-rag-custom-api/env/.env.dev.user.tpl +++ b/templates/ts/custom-copilot-rag-custom-api/env/.env.dev.user.tpl @@ -17,11 +17,16 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY=' ' {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT=' ' {{/azureOpenAIEndpoint}} -AZURE_OPENAI_DEPLOYMENT=' ' {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-custom-api/env/.env.local.user.tpl b/templates/ts/custom-copilot-rag-custom-api/env/.env.local.user.tpl index af91fac080..7b66fcda20 100644 --- a/templates/ts/custom-copilot-rag-custom-api/env/.env.local.user.tpl +++ b/templates/ts/custom-copilot-rag-custom-api/env/.env.local.user.tpl @@ -18,11 +18,16 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY=' ' {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT=' ' {{/azureOpenAIEndpoint}} -AZURE_OPENAI_DEPLOYMENT=' ' {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-custom-api/env/.env.testtool.user.tpl b/templates/ts/custom-copilot-rag-custom-api/env/.env.testtool.user.tpl index 8678ceb266..33ed92af0b 100644 --- a/templates/ts/custom-copilot-rag-custom-api/env/.env.testtool.user.tpl +++ b/templates/ts/custom-copilot-rag-custom-api/env/.env.testtool.user.tpl @@ -17,11 +17,16 @@ SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' {{^azureOpenAIKey}} SECRET_AZURE_OPENAI_API_KEY=' ' {{/azureOpenAIKey}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_MODEL_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} {{#azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' {{/azureOpenAIEndpoint}} {{^azureOpenAIEndpoint}} AZURE_OPENAI_ENDPOINT=' ' {{/azureOpenAIEndpoint}} -AZURE_OPENAI_DEPLOYMENT=' ' {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-custom-api/infra/azure.parameters.json.tpl b/templates/ts/custom-copilot-rag-custom-api/infra/azure.parameters.json.tpl index 81e8292e6f..d8cc443814 100644 --- a/templates/ts/custom-copilot-rag-custom-api/infra/azure.parameters.json.tpl +++ b/templates/ts/custom-copilot-rag-custom-api/infra/azure.parameters.json.tpl @@ -24,7 +24,7 @@ "value": "${{AZURE_OPENAI_ENDPOINT}}" }, "azureOpenAIDeployment": { - "value": "${{AZURE_OPENAI_DEPLOYMENT}}" + "value": "${{AZURE_OPENAI_MODEL_DEPLOYMENT_NAME}}" }, {{/useAzureOpenAI}} "webAppSKU": { diff --git a/templates/ts/custom-copilot-rag-custom-api/package.json.tpl b/templates/ts/custom-copilot-rag-custom-api/package.json.tpl index 3dc5806536..806d201894 100644 --- a/templates/ts/custom-copilot-rag-custom-api/package.json.tpl +++ b/templates/ts/custom-copilot-rag-custom-api/package.json.tpl @@ -36,7 +36,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/custom-copilot-rag-custom-api/teamsapp.local.yml.tpl b/templates/ts/custom-copilot-rag-custom-api/teamsapp.local.yml.tpl index 267e41f9e0..b8a9b239cc 100644 --- a/templates/ts/custom-copilot-rag-custom-api/teamsapp.local.yml.tpl +++ b/templates/ts/custom-copilot-rag-custom-api/teamsapp.local.yml.tpl @@ -86,5 +86,5 @@ deploy: {{#useAzureOpenAI}} AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} - AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_DEPLOYMENT}} + AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_MODEL_DEPLOYMENT_NAME}} {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-custom-api/teamsapp.testtool.yml.tpl b/templates/ts/custom-copilot-rag-custom-api/teamsapp.testtool.yml.tpl index 2c9cc4417c..54a837325e 100644 --- a/templates/ts/custom-copilot-rag-custom-api/teamsapp.testtool.yml.tpl +++ b/templates/ts/custom-copilot-rag-custom-api/teamsapp.testtool.yml.tpl @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command @@ -27,6 +27,6 @@ deploy: {{#useAzureOpenAI}} AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} - AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_DEPLOYMENT}} + AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_MODEL_DEPLOYMENT_NAME}} {{/useAzureOpenAI}} TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/.gitignore b/templates/ts/custom-copilot-rag-customize/.gitignore new file mode 100644 index 0000000000..bc090d9176 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/.gitignore @@ -0,0 +1,22 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +.localConfigs +.localConfigs.testTool +.notification.localstore.json +.notification.testtoolstore.json +appPackage/build + +# dependencies +node_modules/ + +# misc +.env +.deployment +.DS_Store + +# build +lib/ + +# devTools +devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/.localConfigs.testTool.tpl b/templates/ts/custom-copilot-rag-customize/.localConfigs.testTool.tpl new file mode 100644 index 0000000000..ec23f97943 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/.localConfigs.testTool.tpl @@ -0,0 +1,12 @@ +# A gitignored place holder file for local runtime configurations +BOT_ID= +BOT_PASSWORD= +{{#useOpenAI}} +OPENAI_API_KEY= +{{/useOpenAI}} +{{#useAzureOpenAI}} +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/useAzureOpenAI}} +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json diff --git a/templates/ts/custom-copilot-rag-customize/.localConfigs.tpl b/templates/ts/custom-copilot-rag-customize/.localConfigs.tpl new file mode 100644 index 0000000000..acd128167d --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/.localConfigs.tpl @@ -0,0 +1,11 @@ +# A gitignored place holder file for local runtime configurations +BOT_ID= +BOT_PASSWORD= +{{#useOpenAI}} +OPENAI_API_KEY= +{{/useOpenAI}} +{{#useAzureOpenAI}} +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/.vscode/extensions.json b/templates/ts/custom-copilot-rag-customize/.vscode/extensions.json new file mode 100644 index 0000000000..1b70a39308 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension" + ] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/.vscode/launch.json.tpl b/templates/ts/custom-copilot-rag-customize/.vscode/launch.json.tpl new file mode 100644 index 0000000000..9e3b45ee1f --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/.vscode/launch.json.tpl @@ -0,0 +1,122 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Attach to Local Service", + "type": "node", + "request": "attach", + "port": 9239, + "restart": true, + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Teams (Edge)", + "configurations": [ + "Launch App (Edge)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { +{{#enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Chrome)", + "configurations": [ + "Launch App (Chrome)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { +{{#enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} + "order": 2 + }, + "stopAll": true + }, + { + "name": "Debug in Test Tool (Preview)", + "configurations": [ + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App (Test Tool)", + "presentation": { +{{#enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} + "order": 1 + }, + "stopAll": true + } + ] +} diff --git a/templates/ts/custom-copilot-rag-customize/.vscode/settings.json b/templates/ts/custom-copilot-rag-customize/.vscode/settings.json new file mode 100644 index 0000000000..0d3ba10b02 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/.vscode/tasks.json b/templates/ts/custom-copilot-rag-customize/.vscode/tasks.json new file mode 100644 index 0000000000..1c3e241f27 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/.vscode/tasks.json @@ -0,0 +1,204 @@ +// This file is automatically generated by Teams Toolkit. +// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0. +// See https://aka.ms/teamsfx-tasks for details on how to customize each task. +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Teams App (Test Tool)", + "dependsOn": [ + "Validate prerequisites (Test Tool)", + "Deploy (Test Tool)", + "Start application (Test Tool)", + "Start Test Tool", + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites (Test Tool)", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239, // app inspector port for Node.js debugger + 56150, // test tool port + ] + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy (Test Tool)", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "testtool", + } + }, + { + "label": "Start application (Test Tool)", + "type": "shell", + "command": "npm run dev:teamsfx:testtool", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}", + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + }, + { + "label": "Start Test Tool", + "type": "shell", + "command": "npm run dev:teamsfx:launch-testtool", + "isBackground": true, + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin:${env:PATH}" + } + }, + "windows": { + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin;${env:PATH}" + } + } + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Listening on" + } + }, + "presentation": { + "panel": "dedicated", + "reveal": "silent" + } + }, + { + "label": "Start Teams App Locally", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Provision", + "Deploy", + "Start application" + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239 // app inspector port for Node.js debugger + ] + } + }, + { + // Start the local tunnel service to forward public URL to local port and inspect traffic. + // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. + "label": "Start local tunnel", + "type": "teamsfx", + "command": "debug-start-local-tunnel", + "args": { + "type": "dev-tunnel", + "ports": [ + { + "portNumber": 3978, + "protocol": "http", + "access": "public", + "writeToEnvironmentFile": { + "endpoint": "BOT_ENDPOINT", // output tunnel endpoint as BOT_ENDPOINT + "domain": "BOT_DOMAIN" // output tunnel domain as BOT_DOMAIN + } + } + ], + "env": "local" + }, + "isBackground": true, + "problemMatcher": "$teamsfx-local-tunnel-watch" + }, + { + // Create the debug resources. + // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args. + "label": "Provision", + "type": "teamsfx", + "command": "provision", + "args": { + "env": "local" + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "local" + } + }, + { + "label": "Start application", + "type": "shell", + "command": "npm run dev:teamsfx", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + } + ] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/.webappignore b/templates/ts/custom-copilot-rag-customize/.webappignore new file mode 100644 index 0000000000..f79d01ac12 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/.webappignore @@ -0,0 +1,28 @@ +.webappignore +.fx +.deployment +.localConfigs.testTool +.localConfigs +.notification.localstore.json +.notification.testtoolstore.json +.vscode +*.js.map +*.ts.map +*.ts +.git* +.tsbuildinfo +CHANGELOG.md +readme.md +local.settings.json +test +tsconfig.json +.DS_Store +teamsapp.yml +teamsapp.*.yml +/env/ +/node_modules/.bin +/node_modules/ts-node +/node_modules/typescript +/appPackage/ +/infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/README.md.tpl b/templates/ts/custom-copilot-rag-customize/README.md.tpl new file mode 100644 index 0000000000..ef75c60cea --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/README.md.tpl @@ -0,0 +1,86 @@ +# Overview of the Customize RAG Bot template + +This app template is built on top of [Teams AI library](https://aka.ms/teams-ai-library). +It showcases how to build an basic RAG bot in Teams capable of chatting with users but with context provided by customize data source. + +- [Overview of the Customize RAG Bot template](#overview-of-the-customize-rag-bot-template) + - [Get started with the Customize RAG Bot template](#get-started-with-the-customize-rag-bot-template) + - [What's included in the template](#whats-included-in-the-template) + - [Extend the Customize RAG Bot template with more AI capabilities](#extend-the-customize-rag-bot-template-with-more-ai-capabilities) + - [Additional information and references](#additional-information-and-references) + +## Get started with the Customize RAG Bot template + +> **Prerequisites** +> +> To run the AI Search bot template in your local dev machine, you will need: +> +> - [Node.js](https://nodejs.org/), supported versions: 16, 18 +> - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +{{#useOpenAI}} +> - An account with [OpenAI](https://platform.openai.com/). +{{/useOpenAI}} +{{#useAzureOpenAI}} +> - Prepare your own [Azure OpenAI](https://aka.ms/oai/access) resource. +{{/useAzureOpenAI}} + +1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#useOpenAI}} +1. In file *env/.env.testtool.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=`. +{{/useOpenAI}} +{{#useAzureOpenAI}} +1. In file *env/.env.testtool.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_API_KEY=`, endpoint `AZURE_OPENAI_ENDPOINT=` and deployment name `AZURE_OPENAI_DEPLOYMENT_NAME=`. +{{/useAzureOpenAI}} +1. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. Select `Debug in Test Tool (Preview)`. +1. You can send any message to get a response from the bot. + +**Congratulations**! You are running an application that can now interact with users in Teams App Test Tool: + +![AI Search Bot](https://github.com/OfficeDev/TeamsFx/assets/13211513/f56e7602-a5d3-436a-ae01-78546d61717d) + +## What's included in the template + +| Folder | Contents | +| - | - | +| `.vscode` | VSCode files for debugging | +| `appPackage` | Templates for the Teams application manifest | +| `env` | Environment files | +| `infra` | Templates for provisioning Azure resources | +| `src` | The source code for the application | + +The following files can be customized and demonstrate an example implementation to get you started. + +| File | Contents | +| - | - | +|`src/index.ts`| Sets up the bot app server.| +|`src/adapter.ts`| Sets up the bot adapter.| +|`src/config.ts`| Defines the environment variables.| +|`src/prompts/chat/skprompt.txt`| Defines the prompt.| +|`src/prompts/chat/config.json`| Configures the prompt.| +|`src/app/app.ts`| Handles business logics for the RAG bot.| +|`src/app/myDataSource.ts`| Defines the data source.| +|`src/data/*.md`| Raw text data sources.| + +The following are Teams Toolkit specific project files. You can [visit a complete guide on Github](https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview) to understand how Teams Toolkit works. + +| File | Contents | +| - | - | +|`teamsapp.yml`|This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | +|`teamsapp.local.yml`|This overrides `teamsapp.yml` with actions that enable local execution and debugging.| +|`teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool.| + +## Extend the Customize RAG Bot template with more AI capabilities + +You can follow [Build a Basic AI Chatbot in Teams](https://aka.ms/teamsfx-basic-ai-chatbot) to extend the Basic AI Chatbot template with more AI capabilities, like: +- [Customize prompt](https://aka.ms/teamsfx-basic-ai-chatbot#customize-prompt) +- [Customize user input](https://aka.ms/teamsfx-basic-ai-chatbot#customize-user-input) +- [Customize conversation history](https://aka.ms/teamsfx-basic-ai-chatbot#customize-conversation-history) +- [Customize model type](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-type) +- [Customize model parameters](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-parameters) +- [Handle messages with image](https://aka.ms/teamsfx-basic-ai-chatbot#handle-messages-with-image) + +## Additional information and references +- [Teams AI library](https://aka.ms/teams-ai-library) +- [Teams Toolkit Documentations](https://docs.microsoft.com/microsoftteams/platform/toolkit/teams-toolkit-fundamentals) +- [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +- [Teams Toolkit Samples](https://github.com/OfficeDev/TeamsFx-Samples) \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/appPackage/color.png b/templates/ts/custom-copilot-rag-customize/appPackage/color.png new file mode 100644 index 0000000000..2d7e85c9e9 Binary files /dev/null and b/templates/ts/custom-copilot-rag-customize/appPackage/color.png differ diff --git a/templates/ts/custom-copilot-rag-customize/appPackage/manifest.json.tpl b/templates/ts/custom-copilot-rag-customize/appPackage/manifest.json.tpl new file mode 100644 index 0000000000..d7a51bc8fb --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/appPackage/manifest.json.tpl @@ -0,0 +1,46 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", + "manifestVersion": "1.16", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.microsoft.teams.extension", + "developer": { + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/privacy", + "termsOfUseUrl": "https://www.example.com/termofuse" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "{{appName}}${{APP_NAME_SUFFIX}}", + "full": "full name for {{appName}}" + }, + "description": { + "short": "short description for {{appName}}", + "full": "full description for {{appName}}" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "${{BOT_ID}}", + "scopes": [ + "personal", + "team", + "groupchat" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "composeExtensions": [], + "configurableTabs": [], + "staticTabs": [], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/appPackage/outline.png b/templates/ts/custom-copilot-rag-customize/appPackage/outline.png new file mode 100644 index 0000000000..245fa194db Binary files /dev/null and b/templates/ts/custom-copilot-rag-customize/appPackage/outline.png differ diff --git a/templates/ts/custom-copilot-rag-customize/env/.env.dev b/templates/ts/custom-copilot-rag-customize/env/.env.dev new file mode 100644 index 0000000000..4b07861c03 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/env/.env.dev @@ -0,0 +1,16 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/env/.env.dev.user.tpl b/templates/ts/custom-copilot-rag-customize/env/.env.dev.user.tpl new file mode 100644 index 0000000000..ed67f2e2ac --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/env/.env.dev.user.tpl @@ -0,0 +1,32 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/env/.env.local b/templates/ts/custom-copilot-rag-customize/env/.env.local new file mode 100644 index 0000000000..f3a75f8723 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/env/.env.local @@ -0,0 +1,11 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=local +APP_NAME_SUFFIX=local + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_DOMAIN= +BOT_ENDPOINT= \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/env/.env.local.user.tpl b/templates/ts/custom-copilot-rag-customize/env/.env.local.user.tpl new file mode 100644 index 0000000000..b1c0fc39f2 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/env/.env.local.user.tpl @@ -0,0 +1,33 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/env/.env.testtool b/templates/ts/custom-copilot-rag-customize/env/.env.testtool new file mode 100644 index 0000000000..43ce12aad3 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/env/.env.testtool @@ -0,0 +1,8 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=testtool + +# Environment variables used by test tool +TEAMSAPPTESTER_PORT=56150 +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json diff --git a/templates/ts/custom-copilot-rag-customize/env/.env.testtool.user.tpl b/templates/ts/custom-copilot-rag-customize/env/.env.testtool.user.tpl new file mode 100644 index 0000000000..8beb393d16 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/env/.env.testtool.user.tpl @@ -0,0 +1,32 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/infra/azure.bicep.tpl b/templates/ts/custom-copilot-rag-customize/infra/azure.bicep.tpl new file mode 100644 index 0000000000..9a33563951 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/infra/azure.bicep.tpl @@ -0,0 +1,117 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@description('Required when create Azure Bot service') +param botAadAppClientId string + +@secure() +@description('Required by Bot Framework package in your bot project') +param botAadAppClientSecret string + +{{#useOpenAI}} +@secure() +param openAIKey string +{{/useOpenAI}} +{{#useAzureOpenAI}} +@secure() +param azureOpenAIKey string + +@secure() +param azureOpenAIEndpoint string + +@secure() +param azureOpenAIDeploymentName string +{{/useAzureOpenAI}} + +param webAppSKU string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param location string = resourceGroup().location + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + alwaysOn: true + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' // Run Azure App Service from a package file + } + { + name: 'WEBSITE_NODE_DEFAULT_VERSION' + value: '~18' // Set NodeJS version to 18.x for your site + } + { + name: 'RUNNING_ON_AZURE' + value: '1' + } + { + name: 'BOT_ID' + value: botAadAppClientId + } + { + name: 'BOT_PASSWORD' + value: botAadAppClientSecret + } + {{#useOpenAI}} + { + name: 'OPENAI_API_KEY' + value: openAIKey + } + {{/useOpenAI}} + {{#useAzureOpenAI}} + { + name: 'AZURE_OPENAI_API_KEY' + value: azureOpenAIKey + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: azureOpenAIEndpoint + } + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME' + value: azureOpenAIDeploymentName + } + {{/useAzureOpenAI}} + ] + ftpsState: 'FtpsOnly' + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + botAadAppClientId: botAadAppClientId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/templates/ts/custom-copilot-rag-customize/infra/azure.parameters.json.tpl b/templates/ts/custom-copilot-rag-customize/infra/azure.parameters.json.tpl new file mode 100644 index 0000000000..22a8b2bdf6 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/infra/azure.parameters.json.tpl @@ -0,0 +1,37 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "botAadAppClientId": { + "value": "${{BOT_ID}}" + }, + "botAadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + {{#useOpenAI}} + "openAIKey": { + "value": "${{SECRET_OPENAI_API_KEY}}" + }, + {{/useOpenAI}} + {{#useAzureOpenAI}} + "azureOpenAIKey": { + "value": "${{SECRET_AZURE_OPENAI_API_KEY}}" + }, + "azureOpenAIEndpoint": { + "value": "${{AZURE_OPENAI_ENDPOINT}}" + }, + "azureOpenAIDeploymentName": { + "value": "${{AZURE_OPENAI_DEPLOYMENT_NAME}}" + }, + {{/useAzureOpenAI}} + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "{{appName}}" + } + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/infra/botRegistration/azurebot.bicep b/templates/ts/custom-copilot-rag-customize/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..ab67c7a56b --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/infra/botRegistration/azurebot.bicep @@ -0,0 +1,37 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param botAadAppClientId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: botAadAppClientId + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/templates/ts/custom-copilot-rag-customize/infra/botRegistration/readme.md b/templates/ts/custom-copilot-rag-customize/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/package.json.tpl b/templates/ts/custom-copilot-rag-customize/package.json.tpl new file mode 100644 index 0000000000..c59a3f97cf --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/package.json.tpl @@ -0,0 +1,44 @@ +{ + "name": "{{SafeProjectNameLowerCase}}", + "version": "1.0.0", + "msteams": { + "teamsAppId": null + }, + "description": "Microsoft Teams Toolkit RAG Bot Sample with customize data source and Teams AI Library", + "engines": { + "node": "16 || 18" + }, + "author": "Microsoft", + "license": "MIT", + "main": "./lib/src/index.js", + "scripts": { + "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", + "dev:teamsfx:testtool": "env-cmd --silent -f .localConfigs.testTool npm run dev", + "dev:teamsfx:launch-testtool": "env-cmd --silent -f env/.env.testtool teamsapptester start", + "dev": "nodemon --exec node --inspect=9239 --signal SIGINT -r ts-node/register ./src/index.ts", + "build": "tsc --build && shx cp -r ./src/prompts ./lib/src && shx cp -r ./src/data ./lib/src", + "start": "node ./lib/src/index.js", + "test": "echo \"Error: no test specified\" && exit 1", + "watch": "nodemon --exec \"npm run start\"" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "dependencies": { + "@azure/search-documents": "^12.0.0", + "@microsoft/teams-ai": "^1.1.0", + "botbuilder": "^4.20.0", + "openai": "~4.28.4", + "restify": "^10.0.0" + }, + "devDependencies": { + "@types/restify": "^8.5.5", + "@types/node": "^14.0.0", + "env-cmd": "^10.1.0", + "ts-node": "^10.4.0", + "typescript": "^4.4.4", + "nodemon": "^2.0.7", + "shx": "^0.3.3" + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/src/adapter.ts b/templates/ts/custom-copilot-rag-customize/src/adapter.ts new file mode 100644 index 0000000000..1cf10f4bb8 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/src/adapter.ts @@ -0,0 +1,51 @@ +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +import { + CloudAdapter, + ConfigurationBotFrameworkAuthentication, + ConfigurationServiceClientCredentialFactory, +} from "botbuilder"; + +// This bot's main dialog. +import config from "./config"; + +const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication( + {}, + new ConfigurationServiceClientCredentialFactory({ + MicrosoftAppId: config.botId, + MicrosoftAppPassword: process.env.BOT_PASSWORD, + MicrosoftAppType: "MultiTenant", + }) +); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about how bots work. +const adapter = new CloudAdapter(botFrameworkAuthentication); + +// Catch-all for errors. +const onTurnErrorHandler = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. + console.error(`\n [onTurnError] unhandled error: ${error}`); + + // Only send error message for user messages, not for other message types so the bot doesn't spam a channel or chat. + if (context.activity.type === "message") { + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendTraceActivity( + "OnTurnError Trace", + `${error}`, + "https://www.botframework.com/schemas/error", + "TurnError" + ); + + // Send a message to the user + await context.sendActivity("The bot encountered an error or bug."); + await context.sendActivity("To continue to run this bot, please fix the bot source code."); + } +}; + +// Set the onTurnError for the singleton CloudAdapter. +adapter.onTurnError = onTurnErrorHandler; + +export default adapter; diff --git a/templates/ts/custom-copilot-rag-customize/src/app/app.ts.tpl b/templates/ts/custom-copilot-rag-customize/src/app/app.ts.tpl new file mode 100644 index 0000000000..7c512bf12b --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/src/app/app.ts.tpl @@ -0,0 +1,47 @@ +import { MemoryStorage } from "botbuilder"; +import * as path from "path"; +import config from "../config"; + +// See https://aka.ms/teams-ai-library to learn more about the Teams AI library. +import { Application, ActionPlanner, OpenAIModel, PromptManager, TurnState } from "@microsoft/teams-ai"; +import { MyDataSource } from "./myDataSource"; + +// Create AI components +const model = new OpenAIModel({ + {{#useOpenAI}} + apiKey: config.openAIKey, + defaultModel: config.openAIModelName, + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureApiKey: config.azureOpenAIKey, + azureDefaultDeployment: config.azureOpenAIDeploymentName, + azureEndpoint: config.azureOpenAIEndpoint, + {{/useAzureOpenAI}} + + useSystemMessages: true, + logRequests: true, +}); +const prompts = new PromptManager({ + promptsFolder: path.join(__dirname, "../prompts"), +}); +const planner = new ActionPlanner({ + model, + prompts, + defaultPrompt: "chat", +}); + +// Register your data source with planner +const myDataSource = new MyDataSource("my-ai-search"); +myDataSource.init(); +planner.prompts.addDataSource(myDataSource); + +// Define storage and application +const storage = new MemoryStorage(); +const app = new Application({ + storage, + ai: { + planner, + }, +}); + +export default app; diff --git a/templates/ts/custom-copilot-rag-customize/src/app/myDataSource.ts.tpl b/templates/ts/custom-copilot-rag-customize/src/app/myDataSource.ts.tpl new file mode 100644 index 0000000000..15e2a9136d --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/src/app/myDataSource.ts.tpl @@ -0,0 +1,76 @@ +import { DataSource, Memory, RenderedPromptSection, Tokenizer } from "@microsoft/teams-ai"; +import { TurnContext } from "botbuilder"; +import * as path from "path"; +import * as fs from "fs"; + +/** + * A data source that searches through a local directory of files for a given query. + */ +export class MyDataSource implements DataSource { + /** + * Name of the data source. + */ + public readonly name: string; + + /** + * Local data. + */ + private _data: string[]; + + /** + * Creates a new instance of the MyDataSource instance. + */ + public constructor(name: string) { + this.name = name; + } + + /** + * Initializes the data source. + */ + public init() { + const filePath = path.join(__dirname, "../data"); + const files = fs.readdirSync(filePath); + this._data = files.map(file => { + return fs.readFileSync(path.join(filePath, file), "utf-8"); + }); + } + + /** + * Renders the data source as a string of text. + * @remarks + * The returned output should be a string of text that will be injected into the prompt at render time. + * @param context Turn context for the current turn of conversation with the user. + * @param memory An interface for accessing state values. + * @param tokenizer Tokenizer to use when rendering the data source. + * @param maxTokens Maximum number of tokens allowed to be rendered. + * @returns A promise that resolves to the rendered data source. + */ + public async renderData(context: TurnContext, memory: Memory, tokenizer: Tokenizer, maxTokens: number): Promise> { + const query = memory.getValue("temp.input") as string; + if(!query) { + return { output: "", length: 0, tooLong: false }; + } + for (let data of this._data) { + if (data.includes(query)) { + return { output: this.formatDocument(data), length: data.length, tooLong: false }; + } + } + if (query.toLocaleLowerCase().includes("perksplus")) { + return { output: this.formatDocument(this._data[0]), length: this._data[0].length, tooLong: false }; + } else if (query.toLocaleLowerCase().includes("company") || query.toLocaleLowerCase().includes("history")) { + return { output: this.formatDocument(this._data[1]), length: this._data[1].length, tooLong: false }; + } else if (query.toLocaleLowerCase().includes("northwind") || query.toLocaleLowerCase().includes("health")) { + return { output: this.formatDocument(this._data[2]), length: this._data[2].length, tooLong: false }; + } + return { output: "", length: 0, tooLong: false }; + } + + /** + * Formats the result string + * @param result + * @returns + */ + private formatDocument(result: string): string { + return `${result}`; + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/src/config.ts.tpl b/templates/ts/custom-copilot-rag-customize/src/config.ts.tpl new file mode 100644 index 0000000000..3139587162 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/src/config.ts.tpl @@ -0,0 +1,15 @@ +const config = { + botId: process.env.BOT_ID, + botPassword: process.env.BOT_PASSWORD, + {{#useOpenAI}} + openAIKey: process.env.OPENAI_API_KEY, + openAIModelName: "gpt-3.5-turbo", + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureOpenAIKey: process.env.AZURE_OPENAI_API_KEY, + azureOpenAIEndpoint: process.env.AZURE_OPENAI_ENDPOINT, + azureOpenAIDeploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME, + {{/useAzureOpenAI}} +}; + +export default config; diff --git a/templates/ts/custom-copilot-rag-customize/src/data/Contoso Electronics_PerkPlus_Program.md b/templates/ts/custom-copilot-rag-customize/src/data/Contoso Electronics_PerkPlus_Program.md new file mode 100644 index 0000000000..1d97d5117e --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/src/data/Contoso Electronics_PerkPlus_Program.md @@ -0,0 +1,36 @@ +# Contoso Electronics PerksPlus Program + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Overview +Introducing PerksPlus - the ultimate benefits program designed to support the health and wellness of employees. With PerksPlus, employees have the opportunity to expense up to $1000 for fitness-related programs, making it easier and more affordable to maintain a healthy lifestyle. PerksPlus is not only designed to support employees' physical health, but also their mental health. Regular exercise has been shown to reduce stress, improve mood, and enhance overall well-being. With PerksPlus, employees can invest in their health and wellness, while enjoying the peace of mind that comes with knowing they are getting the support they need to lead a healthy life. +What is Covered? + +PerksPlus covers a wide range of fitness activities, including but not limited to: +* Gym memberships +* Personal training sessions +* Yoga and Pilates classes +* Fitness equipment purchases +* Sports team fees +* Health retreats and spas +* Outdoor adventure activities (such as rock climbing, hiking, and kayaking) +* Group fitness classes (such as dance, martial arts, and cycling) +* Virtual fitness programs (such as online yoga and workout classes) + +In addition to the wide range of fitness activities covered by PerksPlus, the program also covers a variety of lessons and experiences that promote health and wellness. Some of the lessons covered under PerksPlus include: +* Skiing and snowboarding lessons +* Scuba diving lessons +* Surfing lessons +* Horseback riding lessons + +These lessons provide employees with the opportunity to try new things, challenge themselves, and improve their physical skills. They are also a great way to relieve stress and have fun while staying active. + +With PerksPlus, employees can choose from a variety of fitness programs to suit their individual needs and preferences. Whether you're looking to improve your physical fitness, reduce stress, or just have some fun, PerksPlus has you covered. + +## What is Not Covered? +In addition to the wide range of activities covered by PerksPlus, there is also a list of things that are not +covered under the program. These include but are not limited to: +* Non-fitness related expenses +* Medical treatments and procedures +* Travel expenses (unless related to a fitness program) +* Food and supplements \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/src/data/Contoso_Electronics_Company_Overview.md b/templates/ts/custom-copilot-rag-customize/src/data/Contoso_Electronics_Company_Overview.md new file mode 100644 index 0000000000..6878a8e204 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/src/data/Contoso_Electronics_Company_Overview.md @@ -0,0 +1,48 @@ +# Contoso Electronics Company Overview + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## History + +Contoso Electronics, a pioneering force in the tech industry, was founded in 1985 by visionary entrepreneurs with a passion for innovation. Over the years, the company has played a pivotal role in shaping the landscape of consumer electronics. + +| Year | Milestone | +|------|-----------| +| 1985 | Company founded with a focus on cutting-edge technology | +| 1990 | Launched the first-ever handheld personal computer | +| 2000 | Introduced groundbreaking advancements in AI and robotics | +| 2015 | Expansion into sustainable and eco-friendly product lines | + +## Company Overview + +At Contoso Electronics, we take pride in fostering a dynamic and inclusive workplace. Our dedicated team of experts collaborates to create innovative solutions that empower and connect people globally. + +### Core Values + +- **Innovation:** Constantly pushing the boundaries of technology. +- **Diversity:** Embracing different perspectives for creative excellence. +- **Sustainability:** Committed to eco-friendly practices in our products. + +## Vacation Perks + +We believe in work-life balance and understand the importance of well-deserved breaks. Our vacation perks are designed to help our employees recharge and return with renewed enthusiasm. + +| Vacation Tier | Duration | Additional Benefits | +|---------------|----------|---------------------| +| Standard | 2 weeks | Health and wellness stipend | +| Senior | 4 weeks | Travel vouchers for a dream destination | +| Executive | 6 weeks | Luxury resort getaway with family | + +## Employee Recognition + +Recognizing the hard work and dedication of our employees is at the core of our culture. Here are some ways we celebrate achievements: + +- Monthly "Innovator of the Month" awards +- Annual gala with awards for outstanding contributions +- Team-building retreats for high-performing departments + +## Join Us! + +Contoso Electronics is always on the lookout for talented individuals who share our passion for innovation. If you're ready to be part of a dynamic team shaping the future of technology, check out our [careers page](http://www.contoso.com) for exciting opportunities. + +[Learn more about Contoso Electronics!](http://www.contoso.com) diff --git a/templates/ts/custom-copilot-rag-customize/src/data/Contoso_Electronics_Plan_Benefits.md b/templates/ts/custom-copilot-rag-customize/src/data/Contoso_Electronics_Plan_Benefits.md new file mode 100644 index 0000000000..9da5c6429d --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/src/data/Contoso_Electronics_Plan_Benefits.md @@ -0,0 +1,37 @@ +# Contoso Electronics Plan and Benefit Packages + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Northwind Health Plus + +Northwind Health Plus is a comprehensive plan that provides comprehensive coverage for medical, vision, and dental services. This plan also offers prescription drug coverage, mental health and substance abuse coverage, and coverage for preventive care services. With Northwind Health Plus, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. + +This plan also offers coverage for emergency services, both in-network and out-of-network. + +## Northwind Standard + +Northwind Standard is a basic plan that provides coverage for medical, vision, and dental services. This plan also offers coverage for preventive care services, as well as prescription drug coverage. With Northwind Standard, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. This plan does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +## Comparison of Plans + +Both plans offer coverage for routine physicals, well-child visits, immunizations, and other preventive care services. The plans also cover preventive care services such as mammograms, colonoscopies, and other cancer screenings. + +Northwind Health Plus offers more comprehensive coverage than Northwind Standard. This plan offers coverage for emergency services, both in-network and out-of-network, as well as mental health and substance abuse coverage. Northwind Standard does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +Both plans offer coverage for prescription drugs. Northwind Health Plus offers a wider range of prescription drug coverage than Northwind Standard. Northwind Health Plus covers generic, brand-name, and specialty drugs, while Northwind Standard only covers generic and brand-name drugs. + +Both plans offer coverage for vision and dental services. Northwind Health Plus offers coverage for vision exams, glasses, and contact lenses, as well as dental exams, cleanings, and fillings. Northwind Standard only offers coverage for vision exams and glasses. + +Both plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays, doctor visits, lab tests, and X-rays. Northwind Standard only offers coverage for doctor visits and lab tests. + +Northwind Health Plus is a comprehensive plan that offers more coverage than Northwind Standard. Northwind Health Plus offers coverage for emergency services, mental health and substance abuse coverage, and out-of-network services, while Northwind Standard does not. Northwind Health Plus also offers a wider range of prescription drug coverage than Northwind Standard. Both plans offer coverage for vision and dental services, as well as medical services. + +## Cost Comparison + +Contoso Electronics deducts the employee's portion of the healthcare cost from each paycheck. This means that the cost of the health insurance will be spread out over the course of the year, rather than being paid in one lump sum. The employee's portion of the cost will be calculated based on the selected health plan and the number of people covered by the insurance. The table below shows a cost comparison between the different health plans offered by Contoso Electronics + +| | Northwind Standard | NorthWind Health Plus | +|---------------|----------|---------------------| +| Employee Only | $45.00 | $55.00 | +| Employee +1 | $65.00 | $71.00 | +| Employee +2 or more | $78.00 | $89.00 | \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/src/index.ts b/templates/ts/custom-copilot-rag-customize/src/index.ts new file mode 100644 index 0000000000..0db519818e --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/src/index.ts @@ -0,0 +1,25 @@ +// Import required packages +import * as restify from "restify"; + +// This bot's adapter +import adapter from "./adapter"; + +// This bot's main dialog. +import app from "./app/app"; + +// Create HTTP server. +const server = restify.createServer(); +server.use(restify.plugins.bodyParser()); + +server.listen(process.env.port || process.env.PORT || 3978, () => { + console.log(`\nBot Started, ${server.name} listening to ${server.url}`); +}); + +// Listen for incoming server requests. +server.post("/api/messages", async (req, res) => { + // Route received a request to adapter for processing + await adapter.process(req, res as any, async (context) => { + // Dispatch to application for routing + await app.run(context); + }); +}); diff --git a/templates/ts/custom-copilot-rag-customize/src/prompts/chat/config.json b/templates/ts/custom-copilot-rag-customize/src/prompts/chat/config.json new file mode 100644 index 0000000000..8bd96c0062 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/src/prompts/chat/config.json @@ -0,0 +1,22 @@ +{ + "schema": 1.1, + "description": "Chat with Teams RAG.", + "type": "completion", + "completion": { + "completion_type": "chat", + "include_history": true, + "include_input": true, + "max_input_tokens": 2800, + "max_tokens": 1000, + "temperature": 0.9, + "top_p": 0.0, + "presence_penalty": 0.6, + "frequency_penalty": 0.0, + "stop_sequences": [] + }, + "augmentation": { + "data_sources": { + "my-ai-search": 1200 + } + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/src/prompts/chat/skprompt.txt b/templates/ts/custom-copilot-rag-customize/src/prompts/chat/skprompt.txt new file mode 100644 index 0000000000..2a2ebee5a3 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/src/prompts/chat/skprompt.txt @@ -0,0 +1,3 @@ +The following is a conversation with an AI assistant, who is an expert on answering questions over the given context. +Responses should be in a short journalistic style with no more than 80 words. +Use the context provided in the `` tags as the source for your answers. \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/teamsapp.local.yml.tpl b/templates/ts/custom-copilot-rag-customize/teamsapp.local.yml.tpl new file mode 100644 index 0000000000..55316677e1 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/teamsapp.local.yml.tpl @@ -0,0 +1,90 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create + with: + botId: ${{BOT_ID}} + name: {{appName}} + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install --no-audit + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs + envs: + BOT_ID: ${{BOT_ID}} + BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + {{#useOpenAI}} + OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/teamsapp.testtool.yml.tpl b/templates/ts/custom-copilot-rag-customize/teamsapp.testtool.yml.tpl new file mode 100644 index 0000000000..52954e85ca --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/teamsapp.testtool.yml.tpl @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.1-beta + symlinkDir: ./devTools/teamsapptester + + # Run npm command + - uses: cli/runNpmCommand + with: + args: install --no-audit + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs.testTool + envs: + {{#useOpenAI}} + OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + {{/useAzureOpenAI}} + TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/teamsapp.yml.tpl b/templates/ts/custom-copilot-rag-customize/teamsapp.yml.tpl new file mode 100644 index 0000000000..80699e1edb --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/teamsapp.yml.tpl @@ -0,0 +1,145 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Teams Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsapp deploy' is executed +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install + - uses: cli/runNpmCommand + name: build app + with: + args: run build --if-present + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: . + # Ignore file location, leave blank will ignore nothing + ignoreFile: .webappignore + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + +# Triggered when 'teamsapp publish' is executed +publish: + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Publish the app to + # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps) + # for review and approval + - uses: teamsApp/publishAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + publishedAppId: TEAMS_APP_PUBLISHED_APP_ID diff --git a/templates/ts/custom-copilot-rag-customize/tsconfig.json b/templates/ts/custom-copilot-rag-customize/tsconfig.json new file mode 100644 index 0000000000..a68afb21f7 --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "declaration": true, + "target": "es2017", + "module": "commonjs", + "outDir": "./lib", + "rootDir": "./", + "sourceMap": true, + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "esModuleInterop": true + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-customize/web.config b/templates/ts/custom-copilot-rag-customize/web.config new file mode 100644 index 0000000000..793a3a982b --- /dev/null +++ b/templates/ts/custom-copilot-rag-customize/web.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/.gitignore b/templates/ts/custom-copilot-rag-microsoft365/.gitignore new file mode 100644 index 0000000000..bc090d9176 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/.gitignore @@ -0,0 +1,22 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +.localConfigs +.localConfigs.testTool +.notification.localstore.json +.notification.testtoolstore.json +appPackage/build + +# dependencies +node_modules/ + +# misc +.env +.deployment +.DS_Store + +# build +lib/ + +# devTools +devTools/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/.localConfigs.tpl b/templates/ts/custom-copilot-rag-microsoft365/.localConfigs.tpl new file mode 100644 index 0000000000..acd128167d --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/.localConfigs.tpl @@ -0,0 +1,11 @@ +# A gitignored place holder file for local runtime configurations +BOT_ID= +BOT_PASSWORD= +{{#useOpenAI}} +OPENAI_API_KEY= +{{/useOpenAI}} +{{#useAzureOpenAI}} +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/.vscode/extensions.json b/templates/ts/custom-copilot-rag-microsoft365/.vscode/extensions.json new file mode 100644 index 0000000000..1b70a39308 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension" + ] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/.vscode/launch.json.tpl b/templates/ts/custom-copilot-rag-microsoft365/.vscode/launch.json.tpl new file mode 100644 index 0000000000..b2248e589a --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/.vscode/launch.json.tpl @@ -0,0 +1,95 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Attach to Local Service" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Attach to Local Service", + "type": "node", + "request": "attach", + "port": 9239, + "restart": true, + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Teams (Edge)", + "configurations": [ + "Launch App (Edge)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { + "group": "1-local", + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Chrome)", + "configurations": [ + "Launch App (Chrome)", + "Attach to Local Service" + ], + "preLaunchTask": "Start Teams App Locally", + "presentation": { + "group": "1-local", + "order": 2 + }, + "stopAll": true + } + ] +} diff --git a/templates/ts/custom-copilot-rag-microsoft365/.vscode/settings.json b/templates/ts/custom-copilot-rag-microsoft365/.vscode/settings.json new file mode 100644 index 0000000000..0d3ba10b02 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/.vscode/tasks.json b/templates/ts/custom-copilot-rag-microsoft365/.vscode/tasks.json new file mode 100644 index 0000000000..585f86ae9a --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/.vscode/tasks.json @@ -0,0 +1,105 @@ +// This file is automatically generated by Teams Toolkit. +// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0. +// See https://aka.ms/teamsfx-tasks for details on how to customize each task. +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Teams App Locally", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Provision", + "Deploy", + "Start application" + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "nodejs", // Validate if Node.js is installed. + "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239 // app inspector port for Node.js debugger + ] + } + }, + { + // Start the local tunnel service to forward public URL to local port and inspect traffic. + // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. + "label": "Start local tunnel", + "type": "teamsfx", + "command": "debug-start-local-tunnel", + "args": { + "type": "dev-tunnel", + "ports": [ + { + "portNumber": 3978, + "protocol": "http", + "access": "public", + "writeToEnvironmentFile": { + "endpoint": "BOT_ENDPOINT", // output tunnel endpoint as BOT_ENDPOINT + "domain": "BOT_DOMAIN" // output tunnel domain as BOT_DOMAIN + } + } + ], + "env": "local" + }, + "isBackground": true, + "problemMatcher": "$teamsfx-local-tunnel-watch" + }, + { + // Create the debug resources. + // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args. + "label": "Provision", + "type": "teamsfx", + "command": "provision", + "args": { + "env": "local" + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "local" + } + }, + { + "label": "Start application", + "type": "shell", + "command": "npm run dev:teamsfx", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "restify listening to|Bot/ME service listening at|[nodemon] app crashed" + } + } + } + ] +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/.webappignore b/templates/ts/custom-copilot-rag-microsoft365/.webappignore new file mode 100644 index 0000000000..18a015a2a3 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/.webappignore @@ -0,0 +1,27 @@ +.webappignore +.fx +.deployment +.localConfigs.testTool +.localConfigs +.notification.localstore.json +.notification.testtoolstore.json +.vscode +*.js.map +*.ts.map +*.ts +.git* +.tsbuildinfo +CHANGELOG.md +readme.md +local.settings.json +test +tsconfig.json +.DS_Store +teamsapp.yml +teamsapp.*.yml +/env/ +/node_modules/.bin +/node_modules/ts-node +/node_modules/typescript +/appPackage/ +/infra/ \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/README.md.tpl b/templates/ts/custom-copilot-rag-microsoft365/README.md.tpl new file mode 100644 index 0000000000..0645b6d775 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/README.md.tpl @@ -0,0 +1,90 @@ +# Overview of the M365 RAG Bot template + +This app template is built on top of [Teams AI library](https://aka.ms/teams-ai-library). +It showcases how to build an RAG bot in Teams capable of chatting with users but with context provided by M365 content from Microsoft Graph Search API. + +- [Overview of the M365 RAG Bot template](#overview-of-the-m365-rag-bot-template) + - [Get started with the M365 RAG bot template](#get-started-with-the-m365-rag-bot-template) + - [What's included in the template](#whats-included-in-the-template) + - [Extend the M365 RAG Bot template with more AI capabilities](#extend-the-m365-rag-bot-template-with-more-ai-capabilities) + - [Additional information and references](#additional-information-and-references) + +## Get started with the M365 RAG Bot template + +> **Prerequisites** +> +> To run the AI Search bot template in your local dev machine, you will need: +> +> - [Node.js](https://nodejs.org/), supported versions: 16, 18 +> - A Microsoft 365 tenant in which you have permission to upload Teams apps. You can get a free Microsoft 365 developer tenant by joining the [Microsoft 365 developer program](https://developer.microsoft.com/en-us/microsoft-365/dev-program). +> - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli). +{{#useOpenAI}} +> - An account with [OpenAI](https://platform.openai.com/). +{{/useOpenAI}} +{{#useAzureOpenAI}} +> - Prepare your own [Azure OpenAI](https://aka.ms/oai/access) resource. +{{/useAzureOpenAI}} + +1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#useOpenAI}} +1. In file *env/.env.local.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=`. +{{/useOpenAI}} +{{#useAzureOpenAI}} +1. In file *env/.env.local.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_API_KEY=`, endpoint `AZURE_OPENAI_ENDPOINT=` and deployment name `AZURE_OPENAI_DEPLOYMENT_NAME=`. +{{/useAzureOpenAI}} +1. Microsoft Graph Search API is available for searching SharePoint content, thus you just need to ensure your document in *src/data/\*.md* is uploaded to SharePoint / OneDrive, no extra data ingestion required. +1. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. +1. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams. +1. You can send any message to get a response from the bot. + +**Congratulations**! You are running an application that can now interact with users in Teams App Test Tool: + +![M365 RAG Bot](https://github.com/OfficeDev/TeamsFx/assets/13211513/c2fff68c-53ce-445a-a101-97f0c127b825) + +## What's included in the template + +| Folder | Contents | +| - | - | +| `.vscode` | VSCode files for debugging | +| `appPackage` | Templates for the Teams application manifest | +| `env` | Environment files | +| `infra` | Templates for provisioning Azure resources | +| `src` | The source code for the application | + +The following files can be customized and demonstrate an example implementation to get you started. + +| File | Contents | +| - | - | +|`src/index.ts`| Sets up the bot app server.| +|`src/adapter.ts`| Sets up the bot adapter.| +|`src/config.ts`| Defines the environment variables.| +|`src/prompts/chat/skprompt.txt`| Defines the prompt.| +|`src/prompts/chat/config.json`| Configures the prompt.| +|`src/app/app.ts`| Handles business logics for the RAG bot.| +|`src/app/m365DataSource.ts`| Defines the m365 data source.| +|`src/data/*.md`| Raw text data sources.| +|`src/public/*.html`| Auth start page and an auth end page to be used by the user sign in flow.| + +The following are Teams Toolkit specific project files. You can [visit a complete guide on Github](https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview) to understand how Teams Toolkit works. + +| File | Contents | +| - | - | +|`teamsapp.yml`|This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | +|`teamsapp.local.yml`|This overrides `teamsapp.yml` with actions that enable local execution and debugging.| +|`teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool.| + +## Extend the M365 RAG Bot template with more AI capabilities + +You can follow [Build a Basic AI Chatbot in Teams](https://aka.ms/teamsfx-basic-ai-chatbot) to extend the Basic AI Chatbot template with more AI capabilities, like: +- [Customize prompt](https://aka.ms/teamsfx-basic-ai-chatbot#customize-prompt) +- [Customize user input](https://aka.ms/teamsfx-basic-ai-chatbot#customize-user-input) +- [Customize conversation history](https://aka.ms/teamsfx-basic-ai-chatbot#customize-conversation-history) +- [Customize model type](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-type) +- [Customize model parameters](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-parameters) +- [Handle messages with image](https://aka.ms/teamsfx-basic-ai-chatbot#handle-messages-with-image) + +## Additional information and references +- [Teams AI library](https://aka.ms/teams-ai-library) +- [Teams Toolkit Documentations](https://docs.microsoft.com/microsoftteams/platform/toolkit/teams-toolkit-fundamentals) +- [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) +- [Teams Toolkit Samples](https://github.com/OfficeDev/TeamsFx-Samples) \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/aad.manifest.json.tpl b/templates/ts/custom-copilot-rag-microsoft365/aad.manifest.json.tpl new file mode 100644 index 0000000000..1ba5cad9c0 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/aad.manifest.json.tpl @@ -0,0 +1,101 @@ +{ + "id": "${{AAD_APP_OBJECT_ID}}", + "appId": "${{AAD_APP_CLIENT_ID}}", + "name": "{{appName}}-aad", + "accessTokenAcceptedVersion": 2, + "signInAudience": "AzureADMyOrg", + "optionalClaims": { + "idToken": [], + "accessToken": [ + { + "name": "idtyp", + "source": null, + "essential": false, + "additionalProperties": [] + } + ], + "saml2Token": [] + }, + "requiredResourceAccess": [ + { + "resourceAppId": "Microsoft Graph", + "resourceAccess": [ + { + "id": "Files.Read.All", + "type": "Scope" + } + ] + } + ], + "oauth2Permissions": [ + { + "adminConsentDescription": "Allows Teams to call the app's web APIs as the current user.", + "adminConsentDisplayName": "Teams can access app's web APIs", + "id": "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}", + "isEnabled": true, + "type": "User", + "userConsentDescription": "Enable Teams to call this app's web APIs with the same rights that you have", + "userConsentDisplayName": "Teams can access app's web APIs and make requests on your behalf", + "value": "access_as_user" + } + ], + "preAuthorizedApplications": [ + { + "appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "d3590ed6-52b3-4102-aeff-aad2292ab01c", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "00000002-0000-0ff1-ce00-000000000000", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "bc59ab01-8403-45c6-8796-ac3ef710b3e3", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "0ec893e0-5785-4de6-99da-4ed124e5296c", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "4765445b-32c6-49b0-83e6-1d93765276ca", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + }, + { + "appId": "4345a7b9-9a63-4910-a426-35363201d503", + "permissionIds": [ + "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}" + ] + } + ], + "identifierUris":[ + "api://botid-${{BOT_ID}}" + ], + "replyUrlsWithType":[ + { + "url": "https://${{BOT_DOMAIN}}/auth-end.html", + "type": "Web" + } + ] +} diff --git a/templates/ts/custom-copilot-rag-microsoft365/appPackage/color.png b/templates/ts/custom-copilot-rag-microsoft365/appPackage/color.png new file mode 100644 index 0000000000..2d7e85c9e9 Binary files /dev/null and b/templates/ts/custom-copilot-rag-microsoft365/appPackage/color.png differ diff --git a/templates/ts/custom-copilot-rag-microsoft365/appPackage/manifest.json.tpl b/templates/ts/custom-copilot-rag-microsoft365/appPackage/manifest.json.tpl new file mode 100644 index 0000000000..34072a4676 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/appPackage/manifest.json.tpl @@ -0,0 +1,52 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", + "manifestVersion": "1.16", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.microsoft.teams.extension", + "developer": { + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/privacy", + "termsOfUseUrl": "https://www.example.com/termofuse" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "{{appName}}${{APP_NAME_SUFFIX}}", + "full": "full name for {{appName}}" + }, + "description": { + "short": "short description for {{appName}}", + "full": "full description for {{appName}}" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "${{BOT_ID}}", + "scopes": [ + "personal", + "team", + "groupchat" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "composeExtensions": [], + "configurableTabs": [], + "staticTabs": [], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [ + "${{BOT_DOMAIN}}" + ], + "webApplicationInfo": { + "id": "${{AAD_APP_CLIENT_ID}}", + "resource": "api://botid-${{BOT_ID}}" + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/appPackage/outline.png b/templates/ts/custom-copilot-rag-microsoft365/appPackage/outline.png new file mode 100644 index 0000000000..245fa194db Binary files /dev/null and b/templates/ts/custom-copilot-rag-microsoft365/appPackage/outline.png differ diff --git a/templates/ts/custom-copilot-rag-microsoft365/env/.env.dev b/templates/ts/custom-copilot-rag-microsoft365/env/.env.dev new file mode 100644 index 0000000000..8cc91bf0c2 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/env/.env.dev @@ -0,0 +1,22 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= +AAD_APP_CLIENT_ID= +AAD_APP_OBJECT_ID= +AAD_APP_TENANT_ID= +AAD_APP_OAUTH_AUTHORITY= +AAD_APP_OAUTH_AUTHORITY_HOST= +AAD_APP_ACCESS_AS_USER_PERMISSION_ID= \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/env/.env.dev.user.tpl b/templates/ts/custom-copilot-rag-microsoft365/env/.env.dev.user.tpl new file mode 100644 index 0000000000..a077330011 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/env/.env.dev.user.tpl @@ -0,0 +1,33 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +SECRET_AAD_APP_CLIENT_SECRET= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/env/.env.local b/templates/ts/custom-copilot-rag-microsoft365/env/.env.local new file mode 100644 index 0000000000..5574fde921 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/env/.env.local @@ -0,0 +1,17 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=local +APP_NAME_SUFFIX=local + +# Generated during provision, you can also add your own variables. +BOT_ID= +TEAMS_APP_ID= +BOT_DOMAIN= +BOT_ENDPOINT= +AAD_APP_CLIENT_ID= +AAD_APP_OBJECT_ID= +AAD_APP_TENANT_ID= +AAD_APP_OAUTH_AUTHORITY= +AAD_APP_OAUTH_AUTHORITY_HOST= +AAD_APP_ACCESS_AS_USER_PERMISSION_ID= \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/env/.env.local.user.tpl b/templates/ts/custom-copilot-rag-microsoft365/env/.env.local.user.tpl new file mode 100644 index 0000000000..f68ff06c67 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/env/.env.local.user.tpl @@ -0,0 +1,34 @@ +# This file includes environment variables that will not be committed to git by default. You can set these environment variables in your CI/CD system for your project. + +# If you're adding a secret value, add SECRET_ prefix to the name so Teams Toolkit can handle them properly +# Secrets. Keys prefixed with `SECRET_` will be masked in Teams Toolkit logs. +SECRET_BOT_PASSWORD= +SECRET_AAD_APP_CLIENT_SECRET= +{{#useOpenAI}} +{{#openAIKey}} +SECRET_OPENAI_API_KEY='{{{openAIKey}}}' +{{/openAIKey}} +{{^openAIKey}} +SECRET_OPENAI_API_KEY= +{{/openAIKey}} +{{/useOpenAI}} +{{#useAzureOpenAI}} +{{#azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY='{{{azureOpenAIKey}}}' +{{/azureOpenAIKey}} +{{^azureOpenAIKey}} +SECRET_AZURE_OPENAI_API_KEY= +{{/azureOpenAIKey}} +{{#azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT='{{{azureOpenAIEndpoint}}}' +{{/azureOpenAIEndpoint}} +{{^azureOpenAIEndpoint}} +AZURE_OPENAI_ENDPOINT= +{{/azureOpenAIEndpoint}} +{{#azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME='{{{azureOpenAIDeploymentName}}}' +{{/azureOpenAIDeploymentName}} +{{^azureOpenAIDeploymentName}} +AZURE_OPENAI_DEPLOYMENT_NAME= +{{/azureOpenAIDeploymentName}} +{{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/infra/azure.bicep.tpl b/templates/ts/custom-copilot-rag-microsoft365/infra/azure.bicep.tpl new file mode 100644 index 0000000000..9488f43122 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/infra/azure.bicep.tpl @@ -0,0 +1,146 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@description('Required when create Azure Bot service') +param botAadAppClientId string + +@secure() +@description('Required by Bot Framework package in your bot project') +param botAadAppClientSecret string + +{{#useOpenAI}} +@secure() +param openAIKey string +{{/useOpenAI}} +{{#useAzureOpenAI}} +@secure() +param azureOpenAIKey string + +@secure() +param azureOpenAIEndpoint string + +@secure() +param azureOpenAIDeploymentName string +{{/useAzureOpenAI}} + +param webAppSKU string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param location string = resourceGroup().location +param aadAppClientId string +param aadAppTenantId string +param aadAppOauthAuthorityHost string +@secure() +param aadAppClientSecret string + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + alwaysOn: true + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' // Run Azure App Service from a package file + } + { + name: 'WEBSITE_NODE_DEFAULT_VERSION' + value: '~18' // Set NodeJS version to 18.x for your site + } + { + name: 'RUNNING_ON_AZURE' + value: '1' + } + { + name: 'BOT_ID' + value: botAadAppClientId + } + { + name: 'BOT_PASSWORD' + value: botAadAppClientSecret + } + {{#useOpenAI}} + { + name: 'OPENAI_API_KEY' + value: openAIKey + } + {{/useOpenAI}} + {{#useAzureOpenAI}} + { + name: 'AZURE_OPENAI_API_KEY' + value: azureOpenAIKey + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: azureOpenAIEndpoint + } + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME' + value: azureOpenAIDeploymentName + } + {{/useAzureOpenAI}} + ] + ftpsState: 'FtpsOnly' + } + } +} + +resource webAppSettings 'Microsoft.Web/sites/config@2021-02-01' = { + name: '${webAppName}/appsettings' + properties: { + WEBSITE_NODE_DEFAULT_VERSION: '~18' + WEBSITE_RUN_FROM_PACKAGE: '1' + BOT_ID: botAadAppClientId + BOT_PASSWORD: botAadAppClientSecret + BOT_DOMAIN: webApp.properties.defaultHostName + AAD_APP_CLIENT_ID: aadAppClientId + AAD_APP_CLIENT_SECRET: aadAppClientSecret + AAD_APP_TENANT_ID: aadAppTenantId + AAD_APP_OAUTH_AUTHORITY_HOST: aadAppOauthAuthorityHost + {{#useOpenAI}} + OPENAI_API_KEY: openAIKey + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: azureOpenAIKey + AZURE_OPENAI_ENDPOINT: azureOpenAIEndpoint + AZURE_OPENAI_DEPLOYMENT_NAME: azureOpenAIDeploymentName + {{/useAzureOpenAI}} + RUNNING_ON_AZURE: '1' + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + botAadAppClientId: botAadAppClientId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName diff --git a/templates/ts/custom-copilot-rag-microsoft365/infra/azure.parameters.json.tpl b/templates/ts/custom-copilot-rag-microsoft365/infra/azure.parameters.json.tpl new file mode 100644 index 0000000000..1fbc1e0ea3 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/infra/azure.parameters.json.tpl @@ -0,0 +1,49 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "botAadAppClientId": { + "value": "${{BOT_ID}}" + }, + "botAadAppClientSecret": { + "value": "${{SECRET_BOT_PASSWORD}}" + }, + {{#useOpenAI}} + "openAIKey": { + "value": "${{SECRET_OPENAI_API_KEY}}" + }, + {{/useOpenAI}} + {{#useAzureOpenAI}} + "azureOpenAIKey": { + "value": "${{SECRET_AZURE_OPENAI_API_KEY}}" + }, + "azureOpenAIEndpoint": { + "value": "${{AZURE_OPENAI_ENDPOINT}}" + }, + "azureOpenAIDeploymentName": { + "value": "${{AZURE_OPENAI_DEPLOYMENT_NAME}}" + }, + {{/useAzureOpenAI}} + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "{{appName}}" + }, + "aadAppClientId": { + "value": "${{AAD_APP_CLIENT_ID}}" + }, + "aadAppClientSecret": { + "value": "${{SECRET_AAD_APP_CLIENT_SECRET}}" + }, + "aadAppTenantId": { + "value": "${{AAD_APP_TENANT_ID}}" + }, + "aadAppOauthAuthorityHost": { + "value": "${{AAD_APP_OAUTH_AUTHORITY_HOST}}" + } + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/infra/botRegistration/azurebot.bicep b/templates/ts/custom-copilot-rag-microsoft365/infra/botRegistration/azurebot.bicep new file mode 100644 index 0000000000..ab67c7a56b --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/infra/botRegistration/azurebot.bicep @@ -0,0 +1,37 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param botAadAppClientId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: botAadAppClientId + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/templates/ts/custom-copilot-rag-microsoft365/infra/botRegistration/readme.md b/templates/ts/custom-copilot-rag-microsoft365/infra/botRegistration/readme.md new file mode 100644 index 0000000000..d5416243cd --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/package.json.tpl b/templates/ts/custom-copilot-rag-microsoft365/package.json.tpl new file mode 100644 index 0000000000..024aec6337 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/package.json.tpl @@ -0,0 +1,45 @@ +{ + "name": "{{SafeProjectNameLowerCase}}", + "version": "1.0.0", + "msteams": { + "teamsAppId": null + }, + "description": "Microsoft Teams Toolkit RAG Bot Sample with Graph API and Teams AI Library", + "engines": { + "node": "16 || 18" + }, + "author": "Microsoft", + "license": "MIT", + "main": "./lib/src/index.js", + "scripts": { + "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev", + "dev:teamsfx:testtool": "env-cmd --silent -f .localConfigs.testTool npm run dev", + "dev:teamsfx:launch-testtool": "env-cmd --silent -f env/.env.testtool teamsapptester start", + "dev": "nodemon --exec node --inspect=9239 --signal SIGINT -r ts-node/register ./src/index.ts", + "build": "tsc --build && shx cp -r ./src/prompts ./lib/src && shx cp -r ./src/public ./lib/src", + "start": "node ./lib/src/index.js", + "test": "echo \"Error: no test specified\" && exit 1", + "watch": "nodemon --exec \"npm run start\"" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "dependencies": { + "@microsoft/microsoft-graph-client": "^3.0.1", + "@azure/search-documents": "^12.0.0", + "@microsoft/teams-ai": "^1.1.0", + "botbuilder": "^4.20.0", + "openai": "~4.28.4", + "restify": "^10.0.0" + }, + "devDependencies": { + "@types/restify": "^8.5.5", + "@types/node": "^16.0.0", + "env-cmd": "^10.1.0", + "ts-node": "^10.4.0", + "typescript": "^4.4.4", + "nodemon": "^2.0.7", + "shx": "^0.3.3" + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/adapter.ts b/templates/ts/custom-copilot-rag-microsoft365/src/adapter.ts new file mode 100644 index 0000000000..1cf10f4bb8 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/adapter.ts @@ -0,0 +1,51 @@ +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +import { + CloudAdapter, + ConfigurationBotFrameworkAuthentication, + ConfigurationServiceClientCredentialFactory, +} from "botbuilder"; + +// This bot's main dialog. +import config from "./config"; + +const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication( + {}, + new ConfigurationServiceClientCredentialFactory({ + MicrosoftAppId: config.botId, + MicrosoftAppPassword: process.env.BOT_PASSWORD, + MicrosoftAppType: "MultiTenant", + }) +); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about how bots work. +const adapter = new CloudAdapter(botFrameworkAuthentication); + +// Catch-all for errors. +const onTurnErrorHandler = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. + console.error(`\n [onTurnError] unhandled error: ${error}`); + + // Only send error message for user messages, not for other message types so the bot doesn't spam a channel or chat. + if (context.activity.type === "message") { + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendTraceActivity( + "OnTurnError Trace", + `${error}`, + "https://www.botframework.com/schemas/error", + "TurnError" + ); + + // Send a message to the user + await context.sendActivity("The bot encountered an error or bug."); + await context.sendActivity("To continue to run this bot, please fix the bot source code."); + } +}; + +// Set the onTurnError for the singleton CloudAdapter. +adapter.onTurnError = onTurnErrorHandler; + +export default adapter; diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/app/app.ts.tpl b/templates/ts/custom-copilot-rag-microsoft365/src/app/app.ts.tpl new file mode 100644 index 0000000000..1e83b6e5a7 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/app/app.ts.tpl @@ -0,0 +1,73 @@ +import { MemoryStorage } from "botbuilder"; +import * as path from "path"; +import config from "../config"; + +// See https://aka.ms/teams-ai-library to learn more about the Teams AI library. +import { Application, ActionPlanner, OpenAIModel, PromptManager, TurnState } from "@microsoft/teams-ai"; +import { GraphDataSource } from "./graphDataSource"; + +// Create AI components +const model = new OpenAIModel({ + {{#useOpenAI}} + apiKey: config.openAIKey, + defaultModel: config.openAIModelName, + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureApiKey: config.azureOpenAIKey, + azureDefaultDeployment: config.azureOpenAIDeploymentName, + azureEndpoint: config.azureOpenAIEndpoint, + {{/useAzureOpenAI}} + + useSystemMessages: true, + logRequests: true, +}); +const prompts = new PromptManager({ + promptsFolder: path.join(__dirname, "../prompts"), +}); +const planner = new ActionPlanner({ + model, + prompts, + defaultPrompt: "chat", +}); + +// Register your data source with planner +const graphDataSource = new GraphDataSource("graph-ai-search"); +planner.prompts.addDataSource(graphDataSource); + +// Define storage and application +const storage = new MemoryStorage(); +const app = new Application({ + storage, + ai: { + planner, + }, + authentication: { + settings: { + graph: { + scopes: ["Files.Read.All"], + msalConfig: { + auth: { + clientId: process.env.AAD_APP_CLIENT_ID, + clientSecret: process.env.AAD_APP_CLIENT_SECRET, + authority: `${process.env.AAD_APP_OAUTH_AUTHORITY_HOST}/${process.env.AAD_APP_TENANT_ID}` + } + }, + signInLink: `https://${process.env.BOT_DOMAIN}/auth-start.html`, + } + }, + autoSignIn: true, + } +}); + +app.authentication.get("graph").onUserSignInSuccess(async (context, state) => { + // Successfully logged in + await context.sendActivity("You are successfully logged in. You can send a new message to talk to the bot."); +}); + +app.authentication.get("graph").onUserSignInFailure(async (context, state, error) => { + // Failed to login + await context.sendActivity("Failed to login"); + await context.sendActivity(`Error message: ${error.message}`); +}); + +export default app; diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/app/graphDataSource.ts.tpl b/templates/ts/custom-copilot-rag-microsoft365/src/app/graphDataSource.ts.tpl new file mode 100644 index 0000000000..507efce353 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/app/graphDataSource.ts.tpl @@ -0,0 +1,134 @@ +import { DataSource, Memory, RenderedPromptSection, Tokenizer } from "@microsoft/teams-ai"; +import { TurnContext } from "botbuilder"; +import { Client, ResponseType } from "@microsoft/microsoft-graph-client"; + +/** + * A data source that searches through Graph API. + */ +export class GraphDataSource implements DataSource { + /** + * Name of the data source. + */ + public readonly name: string; + + /** + * Graph client to make requests to Graph API. + */ + private graphClient: Client; + + /** + * Creates a new instance of the Graph DataSource instance. + */ + public constructor(name: string) { + this.name = name; + } + + /** + * Renders the data source as a string of text. + * @remarks + * The returned output should be a string of text that will be injected into the prompt at render time. + * @param context Turn context for the current turn of conversation with the user. + * @param memory An interface for accessing state values. + * @param tokenizer Tokenizer to use when rendering the data source. + * @param maxTokens Maximum number of tokens allowed to be rendered. + * @returns A promise that resolves to the rendered data source. + */ + public async renderData(context: TurnContext, memory: Memory, tokenizer: Tokenizer, maxTokens: number): Promise> { + const query = memory.getValue("temp.input") as string; + if(!query) { + return { output: "", length: 0, tooLong: false }; + } + if (!this.graphClient) { + this.graphClient = Client.init({ + authProvider: (done) => { + done(null, (memory as any).temp.authTokens["graph"]); + } + }); + } + let graphQuery = query; + if (query.toLocaleLowerCase().includes("perksplus")) { + graphQuery = "perksplus program"; + } else if (query.toLocaleLowerCase().includes("company") || query.toLocaleLowerCase().includes("history")) { + graphQuery = "company history"; + } else if (query.toLocaleLowerCase().includes("northwind") || query.toLocaleLowerCase().includes("health")) { + graphQuery = "northwind health"; + } + + const contentResults = []; + const response = await this.graphClient.api("/search/query").post({ + requests: [ + { + entityTypes: ["driveItem"], + query: { + // Search for markdown files in the user's OneDrive and SharePoint + // The supported file types are listed here: + // https://learn.microsoft.com/sharepoint/technical-reference/default-crawled-file-name-extensions-and-parsed-file-types + queryString: `${graphQuery}`, + }, + // This parameter is required only when searching with application permissions + // https://learn.microsoft.com/graph/search-concept-searchall + // region: "US", + }, + ], + }); + for (const value of response?.value ?? []) { + for (const hitsContainer of value?.hitsContainers ?? []) { + contentResults.push(...(hitsContainer?.hits ?? [])); + } + } + + // Add documents until you run out of tokens + let length = 0, + output = ""; + for (const result of contentResults) { + const rawContent = await this.downloadSharepointFile( + result.resource.webUrl + ); + if (!rawContent) { + continue; + } + let doc = `${rawContent}\n\n`; + let docLength = tokenizer.encode(doc).length; + const remainingTokens = maxTokens - (length + docLength); + if (remainingTokens <= 0) { + break; + } + + // Append do to output + output += doc; + length += docLength; + } + return { output: this.formatDocument(output), length: output.length, tooLong: false }; + } + + /** + * Formats the result string + * @param result + * @returns + */ + private formatDocument(result: string): string { + return `${result}`; + } + + // Download the file from SharePoint + // https://docs.microsoft.com/en-us/graph/api/driveitem-get-content + private async downloadSharepointFile( + contentUrl: string + ): Promise { + const encodedUrl = this.encodeSharepointContentUrl(contentUrl); + const fileContentResponse = await this.graphClient + .api(`/shares/${encodedUrl}/driveItem/content`) + .responseType(ResponseType.TEXT) + .get(); + + return fileContentResponse; + } + + private encodeSharepointContentUrl(webUrl: string): string { + const byteData = Buffer.from(webUrl, "utf-8"); + const base64String = byteData.toString("base64"); + return ( + "u!" + base64String.replace("=", "").replace("/", "_").replace("+", "_") + ); + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/config.ts.tpl b/templates/ts/custom-copilot-rag-microsoft365/src/config.ts.tpl new file mode 100644 index 0000000000..3139587162 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/config.ts.tpl @@ -0,0 +1,15 @@ +const config = { + botId: process.env.BOT_ID, + botPassword: process.env.BOT_PASSWORD, + {{#useOpenAI}} + openAIKey: process.env.OPENAI_API_KEY, + openAIModelName: "gpt-3.5-turbo", + {{/useOpenAI}} + {{#useAzureOpenAI}} + azureOpenAIKey: process.env.AZURE_OPENAI_API_KEY, + azureOpenAIEndpoint: process.env.AZURE_OPENAI_ENDPOINT, + azureOpenAIDeploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME, + {{/useAzureOpenAI}} +}; + +export default config; diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/data/Contoso Electronics_PerkPlus_Program.txt b/templates/ts/custom-copilot-rag-microsoft365/src/data/Contoso Electronics_PerkPlus_Program.txt new file mode 100644 index 0000000000..1d97d5117e --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/data/Contoso Electronics_PerkPlus_Program.txt @@ -0,0 +1,36 @@ +# Contoso Electronics PerksPlus Program + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Overview +Introducing PerksPlus - the ultimate benefits program designed to support the health and wellness of employees. With PerksPlus, employees have the opportunity to expense up to $1000 for fitness-related programs, making it easier and more affordable to maintain a healthy lifestyle. PerksPlus is not only designed to support employees' physical health, but also their mental health. Regular exercise has been shown to reduce stress, improve mood, and enhance overall well-being. With PerksPlus, employees can invest in their health and wellness, while enjoying the peace of mind that comes with knowing they are getting the support they need to lead a healthy life. +What is Covered? + +PerksPlus covers a wide range of fitness activities, including but not limited to: +* Gym memberships +* Personal training sessions +* Yoga and Pilates classes +* Fitness equipment purchases +* Sports team fees +* Health retreats and spas +* Outdoor adventure activities (such as rock climbing, hiking, and kayaking) +* Group fitness classes (such as dance, martial arts, and cycling) +* Virtual fitness programs (such as online yoga and workout classes) + +In addition to the wide range of fitness activities covered by PerksPlus, the program also covers a variety of lessons and experiences that promote health and wellness. Some of the lessons covered under PerksPlus include: +* Skiing and snowboarding lessons +* Scuba diving lessons +* Surfing lessons +* Horseback riding lessons + +These lessons provide employees with the opportunity to try new things, challenge themselves, and improve their physical skills. They are also a great way to relieve stress and have fun while staying active. + +With PerksPlus, employees can choose from a variety of fitness programs to suit their individual needs and preferences. Whether you're looking to improve your physical fitness, reduce stress, or just have some fun, PerksPlus has you covered. + +## What is Not Covered? +In addition to the wide range of activities covered by PerksPlus, there is also a list of things that are not +covered under the program. These include but are not limited to: +* Non-fitness related expenses +* Medical treatments and procedures +* Travel expenses (unless related to a fitness program) +* Food and supplements \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Company_Overview.txt b/templates/ts/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Company_Overview.txt new file mode 100644 index 0000000000..6878a8e204 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Company_Overview.txt @@ -0,0 +1,48 @@ +# Contoso Electronics Company Overview + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## History + +Contoso Electronics, a pioneering force in the tech industry, was founded in 1985 by visionary entrepreneurs with a passion for innovation. Over the years, the company has played a pivotal role in shaping the landscape of consumer electronics. + +| Year | Milestone | +|------|-----------| +| 1985 | Company founded with a focus on cutting-edge technology | +| 1990 | Launched the first-ever handheld personal computer | +| 2000 | Introduced groundbreaking advancements in AI and robotics | +| 2015 | Expansion into sustainable and eco-friendly product lines | + +## Company Overview + +At Contoso Electronics, we take pride in fostering a dynamic and inclusive workplace. Our dedicated team of experts collaborates to create innovative solutions that empower and connect people globally. + +### Core Values + +- **Innovation:** Constantly pushing the boundaries of technology. +- **Diversity:** Embracing different perspectives for creative excellence. +- **Sustainability:** Committed to eco-friendly practices in our products. + +## Vacation Perks + +We believe in work-life balance and understand the importance of well-deserved breaks. Our vacation perks are designed to help our employees recharge and return with renewed enthusiasm. + +| Vacation Tier | Duration | Additional Benefits | +|---------------|----------|---------------------| +| Standard | 2 weeks | Health and wellness stipend | +| Senior | 4 weeks | Travel vouchers for a dream destination | +| Executive | 6 weeks | Luxury resort getaway with family | + +## Employee Recognition + +Recognizing the hard work and dedication of our employees is at the core of our culture. Here are some ways we celebrate achievements: + +- Monthly "Innovator of the Month" awards +- Annual gala with awards for outstanding contributions +- Team-building retreats for high-performing departments + +## Join Us! + +Contoso Electronics is always on the lookout for talented individuals who share our passion for innovation. If you're ready to be part of a dynamic team shaping the future of technology, check out our [careers page](http://www.contoso.com) for exciting opportunities. + +[Learn more about Contoso Electronics!](http://www.contoso.com) diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Plan_Benefits.txt b/templates/ts/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Plan_Benefits.txt new file mode 100644 index 0000000000..9da5c6429d --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/data/Contoso_Electronics_Plan_Benefits.txt @@ -0,0 +1,37 @@ +# Contoso Electronics Plan and Benefit Packages + +*Disclaimer: This document contains information generated using a language model (Azure OpenAI). The information contained in this document is only for demonstration purposes and does not reflect the opinions or beliefs of Microsoft. Microsoft makes no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information contained in this document. All rights reserved to Microsoft.* + +## Northwind Health Plus + +Northwind Health Plus is a comprehensive plan that provides comprehensive coverage for medical, vision, and dental services. This plan also offers prescription drug coverage, mental health and substance abuse coverage, and coverage for preventive care services. With Northwind Health Plus, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. + +This plan also offers coverage for emergency services, both in-network and out-of-network. + +## Northwind Standard + +Northwind Standard is a basic plan that provides coverage for medical, vision, and dental services. This plan also offers coverage for preventive care services, as well as prescription drug coverage. With Northwind Standard, you can choose from a variety of in-network providers, including primary care physicians, specialists, hospitals, and pharmacies. This plan does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +## Comparison of Plans + +Both plans offer coverage for routine physicals, well-child visits, immunizations, and other preventive care services. The plans also cover preventive care services such as mammograms, colonoscopies, and other cancer screenings. + +Northwind Health Plus offers more comprehensive coverage than Northwind Standard. This plan offers coverage for emergency services, both in-network and out-of-network, as well as mental health and substance abuse coverage. Northwind Standard does not offer coverage for emergency services, mental health and substance abuse coverage, or out-of-network services. + +Both plans offer coverage for prescription drugs. Northwind Health Plus offers a wider range of prescription drug coverage than Northwind Standard. Northwind Health Plus covers generic, brand-name, and specialty drugs, while Northwind Standard only covers generic and brand-name drugs. + +Both plans offer coverage for vision and dental services. Northwind Health Plus offers coverage for vision exams, glasses, and contact lenses, as well as dental exams, cleanings, and fillings. Northwind Standard only offers coverage for vision exams and glasses. + +Both plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays, doctor visits, lab tests, and X-rays. Northwind Standard only offers coverage for doctor visits and lab tests. + +Northwind Health Plus is a comprehensive plan that offers more coverage than Northwind Standard. Northwind Health Plus offers coverage for emergency services, mental health and substance abuse coverage, and out-of-network services, while Northwind Standard does not. Northwind Health Plus also offers a wider range of prescription drug coverage than Northwind Standard. Both plans offer coverage for vision and dental services, as well as medical services. + +## Cost Comparison + +Contoso Electronics deducts the employee's portion of the healthcare cost from each paycheck. This means that the cost of the health insurance will be spread out over the course of the year, rather than being paid in one lump sum. The employee's portion of the cost will be calculated based on the selected health plan and the number of people covered by the insurance. The table below shows a cost comparison between the different health plans offered by Contoso Electronics + +| | Northwind Standard | NorthWind Health Plus | +|---------------|----------|---------------------| +| Employee Only | $45.00 | $55.00 | +| Employee +1 | $65.00 | $71.00 | +| Employee +2 or more | $78.00 | $89.00 | \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/index.ts b/templates/ts/custom-copilot-rag-microsoft365/src/index.ts new file mode 100644 index 0000000000..7062c984ab --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/index.ts @@ -0,0 +1,33 @@ +// Import required packages +import * as restify from "restify"; + +// This bot's adapter +import adapter from "./adapter"; + +// This bot's main dialog. +import app from "./app/app"; +import path from "path"; + +// Create HTTP server. +const server = restify.createServer(); +server.use(restify.plugins.bodyParser()); + +server.listen(process.env.port || process.env.PORT || 3978, () => { + console.log(`\nBot Started, ${server.name} listening to ${server.url}`); +}); + +// Listen for incoming server requests. +server.post("/api/messages", async (req, res) => { + // Route received a request to adapter for processing + await adapter.process(req, res as any, async (context) => { + // Dispatch to application for routing + await app.run(context); + }); +}); + +server.get( + "/auth-:name(start|end).html", + restify.plugins.serveStatic({ + directory: path.join(__dirname, "public"), + }) +); diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/prompts/chat/config.json b/templates/ts/custom-copilot-rag-microsoft365/src/prompts/chat/config.json new file mode 100644 index 0000000000..c9e6987a1e --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/prompts/chat/config.json @@ -0,0 +1,22 @@ +{ + "schema": 1.1, + "description": "Chat with Teams RAG.", + "type": "completion", + "completion": { + "completion_type": "chat", + "include_history": true, + "include_input": true, + "max_input_tokens": 2800, + "max_tokens": 1000, + "temperature": 0.9, + "top_p": 0.0, + "presence_penalty": 0.6, + "frequency_penalty": 0.0, + "stop_sequences": [] + }, + "augmentation": { + "data_sources": { + "graph-ai-search": 1200 + } + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/prompts/chat/skprompt.txt b/templates/ts/custom-copilot-rag-microsoft365/src/prompts/chat/skprompt.txt new file mode 100644 index 0000000000..2a2ebee5a3 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/prompts/chat/skprompt.txt @@ -0,0 +1,3 @@ +The following is a conversation with an AI assistant, who is an expert on answering questions over the given context. +Responses should be in a short journalistic style with no more than 80 words. +Use the context provided in the `` tags as the source for your answers. \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/public/auth-end.html b/templates/ts/custom-copilot-rag-microsoft365/src/public/auth-end.html new file mode 100644 index 0000000000..07fe2fa3b2 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/public/auth-end.html @@ -0,0 +1,65 @@ + + + Login End Page + + + + + +
+ + + diff --git a/templates/ts/custom-copilot-rag-microsoft365/src/public/auth-start.html b/templates/ts/custom-copilot-rag-microsoft365/src/public/auth-start.html new file mode 100644 index 0000000000..4a2d258804 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/src/public/auth-start.html @@ -0,0 +1,177 @@ + + + + + Login Start Page + + + + + + + diff --git a/templates/ts/custom-copilot-rag-microsoft365/teamsapp.local.yml.tpl b/templates/ts/custom-copilot-rag-microsoft365/teamsapp.local.yml.tpl new file mode 100644 index 0000000000..3e7e203b0e --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/teamsapp.local.yml.tpl @@ -0,0 +1,113 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +provision: + - uses: aadApp/create # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty + with: + name: {{appName}}-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. + generateClientSecret: true # If the value is false, the action will not generate client secret for you + signInAudience: "AzureADMyOrg" # Authenticate users with a Microsoft work or school account in your organization's Azure AD tenant (for example, single tenant). + writeToEnvironmentFile: # Write the information of created resources into environment file for the specified environment variable(s). + clientId: AAD_APP_CLIENT_ID + clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file + objectId: AAD_APP_OBJECT_ID + tenantId: AAD_APP_TENANT_ID + authority: AAD_APP_OAUTH_AUTHORITY + authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST + + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create + with: + botId: ${{BOT_ID}} + name: {{appName}} + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + - uses: aadApp/update # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update. + with: + manifestPath: ./aad.manifest.json # Relative path to teamsfx folder. Environment variables in manifest will be replaced before apply to AAD app + outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install --no-audit + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs + envs: + BOT_ID: ${{BOT_ID}} + BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}} + BOT_DOMAIN: ${{BOT_DOMAIN}} + AAD_APP_CLIENT_ID: ${{AAD_APP_CLIENT_ID}} + AAD_APP_CLIENT_SECRET: ${{SECRET_AAD_APP_CLIENT_SECRET}} + AAD_APP_TENANT_ID: ${{AAD_APP_TENANT_ID}} + AAD_APP_OAUTH_AUTHORITY_HOST: ${{AAD_APP_OAUTH_AUTHORITY_HOST}} + {{#useOpenAI}} + OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + {{/useOpenAI}} + {{#useAzureOpenAI}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + {{/useAzureOpenAI}} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/teamsapp.yml.tpl b/templates/ts/custom-copilot-rag-microsoft365/teamsapp.yml.tpl new file mode 100644 index 0000000000..4f332829a2 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/teamsapp.yml.tpl @@ -0,0 +1,163 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.3/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: v1.3 + +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: + - uses: aadApp/create # Creates a new Azure Active Directory (AAD) app to authenticate users if the environment variable that stores clientId is empty + with: + name: {{appName}}-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here. + generateClientSecret: true # If the value is false, the action will not generate client secret for you + signInAudience: "AzureADMyOrg" # Authenticate users with a Microsoft work or school account in your organization's Azure AD tenant (for example, single tenant). + writeToEnvironmentFile: # Write the information of created resources into environment file for the specified environment variable(s). + clientId: AAD_APP_CLIENT_ID + clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file + objectId: AAD_APP_OBJECT_ID + tenantId: AAD_APP_TENANT_ID + authority: AAD_APP_OAUTH_AUTHORITY + authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST + + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: BOT_ID + # The Microsoft Entra application's client secret created for bot. + clientSecret: SECRET_BOT_PASSWORD + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Teams Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + - uses: aadApp/update # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update. + with: + manifestPath: ./aad.manifest.json # Relative path to teamsfx folder. Environment variables in manifest will be replaced before apply to AAD app + outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + +# Triggered when 'teamsapp deploy' is executed +deploy: + # Run npm command + - uses: cli/runNpmCommand + name: install dependencies + with: + args: install + - uses: cli/runNpmCommand + name: build app + with: + args: run build --if-present + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: . + # Ignore file location, leave blank will ignore nothing + ignoreFile: .webappignore + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + +# Triggered when 'teamsapp publish' is executed +publish: + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Publish the app to + # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps) + # for review and approval + - uses: teamsApp/publishAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + publishedAppId: TEAMS_APP_PUBLISHED_APP_ID diff --git a/templates/ts/custom-copilot-rag-microsoft365/tsconfig.json b/templates/ts/custom-copilot-rag-microsoft365/tsconfig.json new file mode 100644 index 0000000000..a68afb21f7 --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "declaration": true, + "target": "es2017", + "module": "commonjs", + "outDir": "./lib", + "rootDir": "./", + "sourceMap": true, + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "esModuleInterop": true + } +} \ No newline at end of file diff --git a/templates/ts/custom-copilot-rag-microsoft365/web.config b/templates/ts/custom-copilot-rag-microsoft365/web.config new file mode 100644 index 0000000000..793a3a982b --- /dev/null +++ b/templates/ts/custom-copilot-rag-microsoft365/web.config @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/ts/dashboard-tab/package.json.tpl b/templates/ts/dashboard-tab/package.json.tpl index 642fbf3109..dd9ac8cc78 100644 --- a/templates/ts/dashboard-tab/package.json.tpl +++ b/templates/ts/dashboard-tab/package.json.tpl @@ -18,7 +18,7 @@ "react-scripts": "^5.0.1" }, "devDependencies": { - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@types/react-router-dom": "^5.3.3", diff --git a/templates/ts/default-bot-message-extension/package.json.tpl b/templates/ts/default-bot-message-extension/package.json.tpl index c288c1474d..33dfaa1d77 100644 --- a/templates/ts/default-bot-message-extension/package.json.tpl +++ b/templates/ts/default-bot-message-extension/package.json.tpl @@ -29,7 +29,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/default-bot/.gitignore b/templates/ts/default-bot/.gitignore index 62290c924c..1939c5eccc 100644 --- a/templates/ts/default-bot/.gitignore +++ b/templates/ts/default-bot/.gitignore @@ -17,3 +17,6 @@ node_modules/ # build lib/ + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/ts/default-bot/.webappignore b/templates/ts/default-bot/.webappignore index a4548f4240..f79d01ac12 100644 --- a/templates/ts/default-bot/.webappignore +++ b/templates/ts/default-bot/.webappignore @@ -25,3 +25,4 @@ teamsapp.*.yml /node_modules/typescript /appPackage/ /infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/default-bot/package.json.tpl b/templates/ts/default-bot/package.json.tpl index a9f872bca5..f85f9d0e89 100644 --- a/templates/ts/default-bot/package.json.tpl +++ b/templates/ts/default-bot/package.json.tpl @@ -28,7 +28,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/default-bot/teamsapp.testtool.yml b/templates/ts/default-bot/teamsapp.testtool.yml index bb912b9a9d..3217c43522 100644 --- a/templates/ts/default-bot/teamsapp.testtool.yml +++ b/templates/ts/default-bot/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/link-unfurling/README.md b/templates/ts/link-unfurling/README.md.tpl similarity index 82% rename from templates/ts/link-unfurling/README.md rename to templates/ts/link-unfurling/README.md.tpl index 4efd38d1d5..1c5f947721 100644 --- a/templates/ts/link-unfurling/README.md +++ b/templates/ts/link-unfurling/README.md.tpl @@ -2,21 +2,35 @@ This template showcases an app that unfurls a link into an adaptive card when URLs with a particular domain are pasted into the compose message area in Microsoft Teams or email body in Outlook. +{{#enableMETestToolByDefault}} +![hero-image](https://aka.ms/teams-app-test-tool-link-unfurling-hero-image) +{{/enableMETestToolByDefault}} +{{^enableMETestToolByDefault}} ![hero-image](https://aka.ms/teamsfx-link-unfurling-hero-image) +{{/enableMETestToolByDefault}} ## Get Started with the Link Unfurling app > **Prerequisites** > > - [Node.js](https://nodejs.org/), supported versions: 16, 18 +{{^enableMETestToolByDefault}} > - A Microsoft 365 account. If you do not have Microsoft 365 account, apply one from [Microsoft 365 developer program](https://developer.microsoft.com/microsoft-365/dev-program) +{{/enableMETestToolByDefault}} > - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [TeamsFx CLI](https://aka.ms/teamsfx-toolkit-cli) 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#enableMETestToolByDefault}} +2. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. +3. The browser will pop up to open Teams App Test Tool. +4. Click the "+" button in the input box, select "Link Unfurling" and paste a link ending with `.botframework.com`. You should see an adaptive card unfurled. Click `Send to Conversation` to send it to the current chat or channel. +{{/enableMETestToolByDefault}} +{{^enableMETestToolByDefault}} 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. 3. Press F5 to start debugging which launches your app in Teams or Outlook using a web browser by select a target Microsoft application: `Debug in Teams`, `Debug in Outlook` and click the `Run and Debug` green arrow button. 4. When Teams or Outlook launches in the browser, select the Add button in the dialog to install your app to Teams. 5. Paste a link ending with `.botframework.com` into compose message area in Teams or email body in Outlook. You should see an adaptive card unfurled. +{{/enableMETestToolByDefault}} ## What's included in the template @@ -24,6 +38,7 @@ This template showcases an app that unfurls a link into an adaptive card when UR | -------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `teamsapp.yml` | Main project file describes your application configuration and defines the set of actions to run in each lifecycle stages | | `teamsapp.local.yml` | This overrides `teamsapp.yml` with actions that enable local execution and debugging | +| `teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool. | | `.vscode/` | VSCode files for local debug | | `src/` | The source code for the link unfurling application | | `appPackage/` | Templates for the Teams application manifest | diff --git a/templates/ts/link-unfurling/package.json.tpl b/templates/ts/link-unfurling/package.json.tpl index afa9f5d970..4b4164e710 100644 --- a/templates/ts/link-unfurling/package.json.tpl +++ b/templates/ts/link-unfurling/package.json.tpl @@ -28,7 +28,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/link-unfurling/teamsapp.testtool.yml b/templates/ts/link-unfurling/teamsapp.testtool.yml index eaf11c0c74..3217c43522 100644 --- a/templates/ts/link-unfurling/teamsapp.testtool.yml +++ b/templates/ts/link-unfurling/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.2.0-alpha + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/m365-message-extension/README.md b/templates/ts/m365-message-extension/README.md.tpl similarity index 87% rename from templates/ts/m365-message-extension/README.md rename to templates/ts/m365-message-extension/README.md.tpl index 210b71f8ea..5a6e5efad9 100644 --- a/templates/ts/m365-message-extension/README.md +++ b/templates/ts/m365-message-extension/README.md.tpl @@ -9,12 +9,23 @@ This app template is a search-based [message extension](https://docs.microsoft.c > To run the template in your local dev machine, you will need: > > - [Node.js](https://nodejs.org/), supported versions: 16, 18 +{{^enableMETestToolByDefault}} > - A [Microsoft 365 account for development](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) > - [Set up your dev environment for extending Teams apps across Microsoft 365](https://aka.ms/teamsfx-m365-apps-prerequisites) > Please note that after you enrolled your developer tenant in Office 365 Target Release, it may take couple days for the enrollment to take effect. +{{/enableMETestToolByDefault}} > - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#enableMETestToolByDefault}} +2. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. Select `Debug in Test Tool (Preview)`. +3. To trigger the Message Extension, you can click the `+` in compose message area and select `Search Command` + +**Congratulations**! You are running an application that can now search npm registries in Teams App Test Tool. + +![Search app demo](https://github.com/OfficeDev/TeamsFx/assets/9698542/5275e5bc-492f-4365-b602-5803938a9780) +{{/enableMETestToolByDefault}} +{{^enableMETestToolByDefault}} 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. 3. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. 4. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams. @@ -25,6 +36,7 @@ This app template is a search-based [message extension](https://docs.microsoft.c **Congratulations**! You are running an application that can now search npm registries in Teams and Outlook. ![Search app demo](https://github.com/OfficeDev/TeamsFx/assets/25220706/27fefae9-c51f-49af-a175-c8c9d5a71af0) +{{/enableMETestToolByDefault}} ## What's included in the template @@ -49,6 +61,7 @@ The following are Teams Toolkit specific project files. You can [visit a complet | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `teamsapp.yml` | This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | | `teamsapp.local.yml` | This overrides `teamsapp.yml` with actions that enable local execution and debugging. | +| `teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool. | ## Extend the template diff --git a/templates/ts/m365-message-extension/package.json.tpl b/templates/ts/m365-message-extension/package.json.tpl index e027fedac0..99dbd13013 100644 --- a/templates/ts/m365-message-extension/package.json.tpl +++ b/templates/ts/m365-message-extension/package.json.tpl @@ -31,7 +31,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/m365-message-extension/teamsapp.testtool.yml b/templates/ts/m365-message-extension/teamsapp.testtool.yml index eaf11c0c74..3217c43522 100644 --- a/templates/ts/m365-message-extension/teamsapp.testtool.yml +++ b/templates/ts/m365-message-extension/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.2.0-alpha + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/message-extension-action/.vscode/launch.json.tpl b/templates/ts/message-extension-action/.vscode/launch.json.tpl index a0369287c8..fb267e00ad 100644 --- a/templates/ts/message-extension-action/.vscode/launch.json.tpl +++ b/templates/ts/message-extension-action/.vscode/launch.json.tpl @@ -76,7 +76,7 @@ "group": "group 0: Teams App Test Tool", {{/enableMETestToolByDefault}} {{^enableMETestToolByDefault}} - "group": "group 3: Teams App Test Tool", + "group": "group 2: Teams App Test Tool", {{/enableMETestToolByDefault}} "order": 1 }, @@ -90,7 +90,7 @@ ], "preLaunchTask": "Start Teams App Locally", "presentation": { - "group": "all", + "group": "group 1: Teams", "order": 1 }, "stopAll": true @@ -103,7 +103,7 @@ ], "preLaunchTask": "Start Teams App Locally", "presentation": { - "group": "all", + "group": "group 1: Teams", "order": 2 }, "stopAll": true diff --git a/templates/ts/message-extension-action/README.md b/templates/ts/message-extension-action/README.md.tpl similarity index 87% rename from templates/ts/message-extension-action/README.md rename to templates/ts/message-extension-action/README.md.tpl index 6c882335a6..4425e7f31a 100644 --- a/templates/ts/message-extension-action/README.md +++ b/templates/ts/message-extension-action/README.md.tpl @@ -11,10 +11,21 @@ This app template implements action command that allows you to present your user > To run the template in your local dev machine, you will need: > > - [Node.js](https://nodejs.org/), supported versions: 16, 18 +{{^enableMETestToolByDefault}} > - A [Microsoft 365 account for development](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) +{{/enableMETestToolByDefault}} > - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#enableMETestToolByDefault}} +2. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. +3. To trigger the action command, you can click the `+` under compose message area and select `Action Command`. + +**Congratulations**! You are running an application that can share information in rich format by creating an Adaptive Card in Teams App Test Tool. + +![action-ME](https://github.com/OfficeDev/TeamsFx/assets/9698542/c0afbd89-7fbb-4e73-98a2-f018be4ca88c) +{{/enableMETestToolByDefault}} +{{^enableMETestToolByDefault}} 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. 3. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. 4. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams. @@ -23,6 +34,7 @@ This app template implements action command that allows you to present your user **Congratulations**! You are running an application that can share information in rich format by creating an Adaptive Card in Teams. ![action-ME](https://github.com/OfficeDev/TeamsFx/assets/25220706/378ea4d7-9332-4aec-9f85-59891d086b80) +{{/enableMETestToolByDefault}} ## What's included in the template @@ -47,6 +59,7 @@ The following are Teams Toolkit specific project files. You can [visit a complet | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `teamsapp.yml` | This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | | `teamsapp.local.yml` | This overrides `teamsapp.yml` with actions that enable local execution and debugging. | +| `teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool. | ## Extend the template diff --git a/templates/ts/message-extension-action/package.json.tpl b/templates/ts/message-extension-action/package.json.tpl index 5e670b7827..4c2ba6cff5 100644 --- a/templates/ts/message-extension-action/package.json.tpl +++ b/templates/ts/message-extension-action/package.json.tpl @@ -31,7 +31,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/message-extension-action/teamsapp.testtool.yml b/templates/ts/message-extension-action/teamsapp.testtool.yml index eaf11c0c74..3217c43522 100644 --- a/templates/ts/message-extension-action/teamsapp.testtool.yml +++ b/templates/ts/message-extension-action/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.2.0-alpha + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/message-extension/README.md b/templates/ts/message-extension/README.md.tpl similarity index 85% rename from templates/ts/message-extension/README.md rename to templates/ts/message-extension/README.md.tpl index cbba36e407..4204eb7564 100644 --- a/templates/ts/message-extension/README.md +++ b/templates/ts/message-extension/README.md.tpl @@ -15,10 +15,24 @@ This app template has a search command, an action command and a link unfurling. > To run the template in your local dev machine, you will need: > > - [Node.js](https://nodejs.org/), supported versions: 16, 18 +{{^enableMETestToolByDefault}} > - A [Microsoft 365 account for development](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) +{{/enableMETestToolByDefault}} > - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-toolkit-cli) 1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +{{#enableMETestToolByDefault}} +2. Press F5 to start debugging which launches your app in Teams App Test Tool using a web browser. +3. To trigger the Message Extension to invoke commands: + 1. To trigger search commands, click the `+` in compose message area and select `Search command`. + 2. To trigger action commands, click the `+` in compose message area or `...` above a message and select `Action command`. + 3. To trigger link unfurling, click the `+` in compose message area and select `Link unfurling`. + +![Search app demo](https://github.com/OfficeDev/TeamsFx/assets/9698542/5275e5bc-492f-4365-b602-5803938a9780) + +![action-ME](https://github.com/OfficeDev/TeamsFx/assets/9698542/c0afbd89-7fbb-4e73-98a2-f018be4ca88c) +{{/enableMETestToolByDefault}} +{{^enableMETestToolByDefault}} 2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. 3. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`. 4. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams. @@ -30,6 +44,7 @@ This app template has a search command, an action command and a link unfurling. ![Search app demo](https://user-images.githubusercontent.com/11220663/167868361-40ffaaa3-0300-4313-ae22-0f0bab49c329.png) ![action-ME](https://github.com/OfficeDev/TeamsFx/assets/25220706/378ea4d7-9332-4aec-9f85-59891d086b80) +{{/enableMETestToolByDefault}} ## What's included in the template @@ -53,6 +68,7 @@ The following are Teams Toolkit specific project files. You can [visit a complet | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `teamsapp.yml` | This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | | `teamsapp.local.yml` | This overrides `teamsapp.yml` with actions that enable local execution and debugging. | +| `teamsapp.testtool.yml`| This overrides `teamsapp.yml` with actions that enable local execution and debugging in Teams App Test Tool. | ## Extend the template diff --git a/templates/ts/message-extension/package.json.tpl b/templates/ts/message-extension/package.json.tpl index 4d9040d1e3..b91e0372e7 100644 --- a/templates/ts/message-extension/package.json.tpl +++ b/templates/ts/message-extension/package.json.tpl @@ -31,7 +31,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/message-extension/teamsapp.testtool.yml b/templates/ts/message-extension/teamsapp.testtool.yml index eaf11c0c74..3217c43522 100644 --- a/templates/ts/message-extension/teamsapp.testtool.yml +++ b/templates/ts/message-extension/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.2.0-alpha + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/non-sso-tab-default-bot/bot/package.json.tpl b/templates/ts/non-sso-tab-default-bot/bot/package.json.tpl index 5a124dce6f..0c282e6536 100644 --- a/templates/ts/non-sso-tab-default-bot/bot/package.json.tpl +++ b/templates/ts/non-sso-tab-default-bot/bot/package.json.tpl @@ -27,7 +27,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "ts-node": "^10.4.0", "typescript": "^4.4.4", diff --git a/templates/ts/non-sso-tab-default-bot/tab/package.json.tpl b/templates/ts/non-sso-tab-default-bot/tab/package.json.tpl index 9964e68f1a..07eccc508a 100644 --- a/templates/ts/non-sso-tab-default-bot/tab/package.json.tpl +++ b/templates/ts/non-sso-tab-default-bot/tab/package.json.tpl @@ -17,7 +17,7 @@ "react-scripts": "^5.0.1" }, "devDependencies": { - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@types/react-router-dom": "^5.3.3", diff --git a/templates/ts/notification-http-timer-trigger/teamsapp.testtool.yml b/templates/ts/notification-http-timer-trigger/teamsapp.testtool.yml index d0ae53af63..75484d7559 100644 --- a/templates/ts/notification-http-timer-trigger/teamsapp.testtool.yml +++ b/templates/ts/notification-http-timer-trigger/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester func: version: ~4.0.5174 diff --git a/templates/ts/notification-http-trigger/teamsapp.testtool.yml b/templates/ts/notification-http-trigger/teamsapp.testtool.yml index d0ae53af63..75484d7559 100644 --- a/templates/ts/notification-http-trigger/teamsapp.testtool.yml +++ b/templates/ts/notification-http-trigger/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester func: version: ~4.0.5174 diff --git a/templates/ts/notification-restify/.appserviceignore b/templates/ts/notification-restify/.appserviceignore index 24f5be9bcf..81e4ebc4dd 100644 --- a/templates/ts/notification-restify/.appserviceignore +++ b/templates/ts/notification-restify/.appserviceignore @@ -25,3 +25,4 @@ teamsapp.*.yml /node_modules/typescript /appPackage/ /infra/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/notification-restify/.gitignore b/templates/ts/notification-restify/.gitignore index 01faebf252..dfb975ac86 100644 --- a/templates/ts/notification-restify/.gitignore +++ b/templates/ts/notification-restify/.gitignore @@ -21,3 +21,6 @@ lib/ .localConfigs .notification.localstore.json .notification.testtoolstore.json + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/ts/notification-restify/package.json.tpl b/templates/ts/notification-restify/package.json.tpl index 7bd4fda773..8bffc31a86 100644 --- a/templates/ts/notification-restify/package.json.tpl +++ b/templates/ts/notification-restify/package.json.tpl @@ -30,7 +30,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "nodemon": "^2.0.7", "ts-node": "^10.4.0", diff --git a/templates/ts/notification-restify/teamsapp.testtool.yml b/templates/ts/notification-restify/teamsapp.testtool.yml index bb912b9a9d..3217c43522 100644 --- a/templates/ts/notification-restify/teamsapp.testtool.yml +++ b/templates/ts/notification-restify/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command diff --git a/templates/ts/notification-timer-trigger/teamsapp.testtool.yml b/templates/ts/notification-timer-trigger/teamsapp.testtool.yml index d0ae53af63..75484d7559 100644 --- a/templates/ts/notification-timer-trigger/teamsapp.testtool.yml +++ b/templates/ts/notification-timer-trigger/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester func: version: ~4.0.5174 diff --git a/templates/ts/sso-tab-with-obo-flow/package.json.tpl b/templates/ts/sso-tab-with-obo-flow/package.json.tpl index 9f23cf8eb1..6de2c7bd9b 100644 --- a/templates/ts/sso-tab-with-obo-flow/package.json.tpl +++ b/templates/ts/sso-tab-with-obo-flow/package.json.tpl @@ -17,7 +17,7 @@ "react-scripts": "^5.0.1" }, "devDependencies": { - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@types/react-router-dom": "^5.3.3", diff --git a/templates/ts/workflow/.appserviceignore b/templates/ts/workflow/.appserviceignore index 3b74678660..d3ef2d0ddc 100644 --- a/templates/ts/workflow/.appserviceignore +++ b/templates/ts/workflow/.appserviceignore @@ -26,3 +26,4 @@ teamsapp.*.yml /appPackage/ /infra/ /templates/ +/devTools/ \ No newline at end of file diff --git a/templates/ts/workflow/.gitignore b/templates/ts/workflow/.gitignore index 01faebf252..dfb975ac86 100644 --- a/templates/ts/workflow/.gitignore +++ b/templates/ts/workflow/.gitignore @@ -21,3 +21,6 @@ lib/ .localConfigs .notification.localstore.json .notification.testtoolstore.json + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/templates/ts/workflow/package.json.tpl b/templates/ts/workflow/package.json.tpl index 296e876422..769f9ea09f 100644 --- a/templates/ts/workflow/package.json.tpl +++ b/templates/ts/workflow/package.json.tpl @@ -30,7 +30,7 @@ }, "devDependencies": { "@types/restify": "^8.5.5", - "@types/node": "^14.0.0", + "@types/node": "^18.0.0", "env-cmd": "^10.1.0", "nodemon": "^2.0.7", "shx": "^0.3.4", diff --git a/templates/ts/workflow/teamsapp.testtool.yml b/templates/ts/workflow/teamsapp.testtool.yml index bb912b9a9d..3217c43522 100644 --- a/templates/ts/workflow/teamsapp.testtool.yml +++ b/templates/ts/workflow/teamsapp.testtool.yml @@ -8,7 +8,7 @@ deploy: - uses: devTool/install with: testTool: - version: ~0.1.0-beta + version: ~0.2.1-beta symlinkDir: ./devTools/teamsapptester # Run npm command