From 9d8ffc180dbac69294ba2d3b76d752302f7fe342 Mon Sep 17 00:00:00 2001 From: Crash Collison <3751389+tehcrashxor@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:04:32 -0800 Subject: [PATCH 1/8] PAC update to v1.29.10 (#782) * PAC update for data import/export and paportal upload/download commands * version bump --- README.md | 5 ++++- gulpfile.mjs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a8ab8c96..b733485d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,10 @@ Installing this extension will also make the latest Power Platform CLI (aka pac) ## Release Notes -2.0.20: +2.0.23: + - pac CLI 1.29.10, (Update to October refresh to fix data import/export and paportal upload/download commands. See release notes on [nuget.org](https://www.nuget.org/packages/Microsoft.PowerApps.CLI/)) + +2.0.21: - pac CLI 1.29.6 (October refresh, see release notes on [nuget.org](https://www.nuget.org/packages/Microsoft.PowerApps.CLI/)) 2.0.13: diff --git a/gulpfile.mjs b/gulpfile.mjs index 947ddc47..f649b640 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -336,7 +336,7 @@ async function snapshot() { } const feedName = 'CAP_ISVExp_Tools_Stable'; -const cliVersion = '1.29.6'; +const cliVersion = '1.29.10'; const recompile = gulp.series( clean, From c5d4f8dc56fc52b3de12f71d3bddc51af8b9c8f2 Mon Sep 17 00:00:00 2001 From: Ritik Ramuka <56073559+ritikramuka@users.noreply.github.com> Date: Thu, 30 Nov 2023 00:01:07 +0530 Subject: [PATCH 2/8] passing aadObject Id in container in user data map instead of user id to consume in graph client (#784) --- src/web/client/common/worker/webworker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/web/client/common/worker/webworker.js b/src/web/client/common/worker/webworker.js index f7107740..6461f74b 100644 --- a/src/web/client/common/worker/webworker.js +++ b/src/web/client/common/worker/webworker.js @@ -115,7 +115,7 @@ async function loadContainer(config, swpId, entityInfo) { self.postMessage({ type: "client-data", userName: otherUser.userName, - userId: key, + userId: otherUser.additionalDetails.AadObjectId, containerId: swpId, entityId: userEntityIdArray, }); @@ -141,7 +141,7 @@ async function loadContainer(config, swpId, entityInfo) { // eslint-disable-next-line no-undef await self.postMessage({ type: "client-data", - userId: changed.key, + userId: otherUser.additionalDetails.AadObjectId, userName: otherUser.userName, containerId: swpId, entityId: userEntityIdArray, From da2cda14de1c2a97dfece36cb5ca9fb9a079c362 Mon Sep 17 00:00:00 2001 From: Ritik Ramuka <56073559+ritikramuka@users.noreply.github.com> Date: Thu, 30 Nov 2023 01:01:10 +0530 Subject: [PATCH 3/8] Quick Pick for Present Users on Studio or vscode side (#774) * added gulp file and webpack config file to compile copresence worker script * dependencies installed * updated gulp file and webpack file to compile webworker script * passed entity id while loading container & few nit changes * creating web worker instance get or create sharedworkspace for copresence * sending message to worker on tab change rather than open active text editor * moved co-presence on vscode for web related parameters to a single place * disabled co-presence feature for users as under development * update in way of joining path * added const for worker event messages * renamed a worker event mesages * remove redundant event registery of onDidChangeTabs * moved populateSharedWorkSpace to authenticateAndUpdateDataverseProperties as no need to call it again and again with file save, and added concurreny handler to api call and telemetry to track logs * disabling co-presence feature hard coded * added worker in webextension context * used concurrency handler to make fetch call worker script * eslint error fixed * adding telemetry and moving api path in constant * removed console log * updating telemetry log * resolving bug of correct api call * telemetry error logs hard coded * added person action icon on editor title bar and quick pick * Quick Pick Provider * Filter logic to get users of current page --------- Co-authored-by: tyaginidhi --- package.json | 10 ++++ src/web/client/WebExtensionContext.ts | 8 ++- src/web/client/common/constants.ts | 1 + src/web/client/extension.ts | 2 + src/web/client/webViews/QuickPickProvider.ts | 54 ++++++++++++++++++++ 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/web/client/webViews/QuickPickProvider.ts diff --git a/package.json b/package.json index 0166192b..1f8868e3 100644 --- a/package.json +++ b/package.json @@ -342,6 +342,11 @@ "command": "powerpages.copilot.explain", "category": "Copilot In Power Pages", "title": "Explain" + }, + { + "command": "powerPlatform.previewCurrentActiveUsers", + "title": "Current Active Users", + "icon": "$(person)" } ], "configuration": { @@ -623,6 +628,11 @@ "command": "microsoft-powerapps-portals.preview-show", "alt": "microsoft-powerapps-portals.preview-show", "group": "navigation" + }, + { + "command": "powerPlatform.previewCurrentActiveUsers", + "alt": "current active users", + "group": "navigation" } ], "commandPalette": [ diff --git a/src/web/client/WebExtensionContext.ts b/src/web/client/WebExtensionContext.ts index 68a0b654..3cd6f2f5 100644 --- a/src/web/client/WebExtensionContext.ts +++ b/src/web/client/WebExtensionContext.ts @@ -31,6 +31,7 @@ import { ConcurrencyHandler } from "./dal/concurrencyHandler"; import { isMultifileEnabled } from "./utilities/commonUtil"; import { UserDataMap } from "./context/userDataMap"; import { EntityForeignKeyDataMap } from "./context/entityForeignKeyDataMap"; +import { QuickPickProvider } from "./webViews/QuickPickProvider"; export interface IWebExtensionContext { // From portalSchema properties @@ -110,6 +111,7 @@ class WebExtensionContext implements IWebExtensionContext { private _sharedWorkSpaceMap: Map; private _containerId: string; private _connectedUsers: UserDataMap; + private _quickPickProvider: QuickPickProvider; public get schemaDataSourcePropertiesMap() { return this._schemaDataSourcePropertiesMap; @@ -210,7 +212,10 @@ class WebExtensionContext implements IWebExtensionContext { public set containerId(containerId: string) { this._containerId = containerId; } - + public get quickPickProvider() { + return this._quickPickProvider; + } + constructor() { this._schemaDataSourcePropertiesMap = new Map(); this._schemaEntitiesMap = new Map>(); @@ -243,6 +248,7 @@ class WebExtensionContext implements IWebExtensionContext { this._sharedWorkSpaceMap = new Map(); this._containerId = ""; this._connectedUsers = new UserDataMap(); + this._quickPickProvider = new QuickPickProvider(); } public setWebExtensionContext( diff --git a/src/web/client/common/constants.ts b/src/web/client/common/constants.ts index 63ba7de6..67c256d7 100644 --- a/src/web/client/common/constants.ts +++ b/src/web/client/common/constants.ts @@ -34,6 +34,7 @@ export const WEB_EXTENSION_FETCH_WORKER_SCRIPT_FAILED = "Web extension fetch wor export const WEB_EXTENSION_POPULATE_SHARED_WORKSPACE_SYSTEM_ERROR = "Web extension populate shared workspace system error"; export const WEB_EXTENSION_WEB_WORKER_REGISTRATION_FAILED = "Web extension web worker registration failed"; export const WEB_EXTENSION_FETCH_GET_OR_CREATE_SHARED_WORK_SPACE_ERROR = "Web extension fetch get or create shared workspace error"; +export const WEB_EXTENSION_QUICK_PICK_DEFAULT_STRING = "No users are currently viewing this page"; // Web extension constants export const BASE_64 = 'base64'; diff --git a/src/web/client/extension.ts b/src/web/client/extension.ts index 98d110fe..4832566b 100644 --- a/src/web/client/extension.ts +++ b/src/web/client/extension.ts @@ -265,6 +265,7 @@ export function processWorkspaceStateChanges(context: vscode.ExtensionContext) { entityInfo }); } + WebExtensionContext.quickPickProvider.updateQuickPickItems(entityInfo); } } } @@ -300,6 +301,7 @@ export function processWillSaveDocument(context: vscode.ExtensionContext) { export function processWillStartCollaboartion(context: vscode.ExtensionContext) { // feature in progress, hence disabling it if (isCoPresenceEnabled()) { + vscode.commands.registerCommand('powerPlatform.previewCurrentActiveUsers', () => WebExtensionContext.quickPickProvider.showQuickPick()); createWebWorkerInstance(context); } } diff --git a/src/web/client/webViews/QuickPickProvider.ts b/src/web/client/webViews/QuickPickProvider.ts new file mode 100644 index 00000000..f86b5b60 --- /dev/null +++ b/src/web/client/webViews/QuickPickProvider.ts @@ -0,0 +1,54 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import * as vscode from "vscode"; +import WebExtensionContext from "../WebExtensionContext"; +import { IEntityInfo } from "../common/interfaces"; +import * as Constants from "../common/constants"; + +export class QuickPickProvider { + private items: vscode.QuickPickItem[] = []; + + constructor() { + this.items = new Array(); + } + + public async updateQuickPickItems(entityInfo: IEntityInfo) { + const connectedUsersMap = WebExtensionContext.connectedUsers.getUserMap; + + const currentUsers: vscode.QuickPickItem[] = []; + + Array.from( + connectedUsersMap.entries() + ).map(([, value]) => { + if (value._entityId.length) { + value._entityId.forEach(async (entityId) => { + const contentPageId = WebExtensionContext.entityForeignKeyDataMap.getEntityForeignKeyMap.get(`${entityId}`); + + if ( + contentPageId && + contentPageId.has(`${entityInfo.entityId}`) + ) { + currentUsers.push({ + label: value._userName, + }); + } + }) + } + }); + + if (currentUsers.length) { + this.items = currentUsers; + } else { + this.items = [{ + label: Constants.WEB_EXTENSION_QUICK_PICK_DEFAULT_STRING, + }]; + } + } + + public showQuickPick() { + vscode.window.showQuickPick(this.items); + } +} From beb322898e8a6d3b2ef3123689227dcf826f52ec Mon Sep 17 00:00:00 2001 From: Crash Collison <3751389+tehcrashxor@users.noreply.github.com> Date: Thu, 30 Nov 2023 09:54:23 -0800 Subject: [PATCH 4/8] PAC update to v1.29.11 (#786) --- README.md | 4 ++-- gulpfile.mjs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b733485d..987b6a4d 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ Installing this extension will also make the latest Power Platform CLI (aka pac) ## Release Notes -2.0.23: - - pac CLI 1.29.10, (Update to October refresh to fix data import/export and paportal upload/download commands. See release notes on [nuget.org](https://www.nuget.org/packages/Microsoft.PowerApps.CLI/)) +2.0.25: + - pac CLI 1.29.11, (Update to October refresh to fix data import/export and paportal upload/download commands. See release notes on [nuget.org](https://www.nuget.org/packages/Microsoft.PowerApps.CLI/)) 2.0.21: - pac CLI 1.29.6 (October refresh, see release notes on [nuget.org](https://www.nuget.org/packages/Microsoft.PowerApps.CLI/)) diff --git a/gulpfile.mjs b/gulpfile.mjs index f649b640..c951cf8c 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -336,7 +336,7 @@ async function snapshot() { } const feedName = 'CAP_ISVExp_Tools_Stable'; -const cliVersion = '1.29.10'; +const cliVersion = '1.29.11'; const recompile = gulp.series( clean, From a5d2c4657393057c1aa5daf6fc381ede5718515e Mon Sep 17 00:00:00 2001 From: toshio-msft <93787062+toshio-msft@users.noreply.github.com> Date: Thu, 30 Nov 2023 10:05:05 -0800 Subject: [PATCH 5/8] powerplatform-vscode Localization hand-back [CTAS - RunID=20231120-100923-kwyyxzgyd] (#775) * [powerplatform-vscode] Localization hand-back [CTAS - RunID=20231120-100923-kwyyxzgyd] * Extracted loc artifacts --------- Co-authored-by: Crash Collison <3751389+tehcrashxor@users.noreply.github.com> Co-authored-by: Andrew Petrochuk <30735471+petrochuk@users.noreply.github.com> --- l10n/bundle.l10n.cs.json | 7 ++++- l10n/bundle.l10n.de.json | 1 - l10n/bundle.l10n.es.json | 7 ++++- l10n/bundle.l10n.fr.json | 7 ++++- l10n/bundle.l10n.it.json | 7 ++++- l10n/bundle.l10n.ja.json | 7 ++++- l10n/bundle.l10n.json | 6 ++--- l10n/bundle.l10n.ko.json | 7 ++++- l10n/bundle.l10n.pt-br.json | 7 ++++- l10n/bundle.l10n.ru.json | 7 ++++- l10n/bundle.l10n.tr.json | 7 ++++- l10n/bundle.l10n.zh-cn.json | 7 ++++- l10n/bundle.l10n.zh-tw.json | 7 ++++- .../vscode-powerplatform.cs.xlf | 24 +++++++++++++++-- .../vscode-powerplatform.de.xlf | 20 +++++++++++--- .../vscode-powerplatform.es.xlf | 24 +++++++++++++++-- .../vscode-powerplatform.fr.xlf | 26 ++++++++++++++++--- .../vscode-powerplatform.it.xlf | 24 +++++++++++++++-- .../vscode-powerplatform.ja.xlf | 26 ++++++++++++++++--- .../vscode-powerplatform.ko.xlf | 24 +++++++++++++++-- .../vscode-powerplatform.pt-BR.xlf | 26 ++++++++++++++++--- .../vscode-powerplatform.ru.xlf | 24 +++++++++++++++-- .../vscode-powerplatform.tr.xlf | 26 ++++++++++++++++--- .../vscode-powerplatform.zh-CN.xlf | 26 ++++++++++++++++--- .../vscode-powerplatform.zh-TW.xlf | 26 ++++++++++++++++--- 25 files changed, 334 insertions(+), 46 deletions(-) diff --git a/l10n/bundle.l10n.cs.json b/l10n/bundle.l10n.cs.json index 725a0803..6e4baa87 100644 --- a/l10n/bundle.l10n.cs.json +++ b/l10n/bundle.l10n.cs.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "Zkopírováno do schránky", "Creating {0}.../{0} will be replaced by the entity type.": "Vytváří se {0}...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "Zobrazované jméno: {0}\nJedinečné jméno: {1}\nVerze: {2}\nTyp: {3}", + "Do not show again": "Příště nezobrazovat", "Edit the site": "Upravit web", "Enter Organization ID": "Zadejte kód organizace.", "Enter name": "Zadejte název.", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "Na soubor zde může být odkazováno názvem {0}.", "File(s) already exist. No new files to add": "Soubory už existují. Nemáte žádné nové soubory k přidání.", "Installing Power Pages generator(v{0}).../{0} represents the version number": "Instaluje se generátor Power Pages (verze {0})...", + "Learn more about Copilot": "Další informace o Copilotovi", + "Let Copilot help you code": "Copilot vám pomůže s kódem", "Managed": "Spravováno", "Maximum 30 characters allowed": "Je povoleno maximálně 30 znaků.", "Microsoft wants your feeback": "Microsoft chce znát váš názor", @@ -67,13 +70,14 @@ "Saving your file ...": "Ukládá se soubor...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Vyberte složku pro nový ovládací prvek PCF.", "Select Type": "Vybrat typ", - "Selection is empty!": "Výběr je prázdný.", + "Selection is empty.": "Výběr je prázdný.", "The Power Pages generator is ready for use in your VS Code extension!": "Generátor Power Pages je připraven k použití v rozšíření editoru VS Code.", "The name you want to give to this authentication profile": "Název, který chcete dát tomuto ověřovacímu profilu", "The pac CLI is ready for use in your VS Code terminal!": "Tento pac CLI je připraven k použití v terminálu editoru VS Code.", "There was a permissions problem with the server": "Na serveru došlo k problému s oprávněními.", "There was a problem opening the workspace": "Při otevírání pracovního prostoru se vyskytl problém.", "There’s a problem on the back end": "U back-endu se vyskytl problém.", + "Try Copilot for Power Pages": "Vyzkoušejte Copilota pro Power Pages", "Try again": "Zkusit znovu", "Unable to complete the request": "Žádost nelze dokončit.", "Unable to find that app": "Tuto aplikaci nelze najít.", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "Při přípravě souboru k úpravám došlo k chybě.", "Web files": "Webové soubory", "Webfile(s) added successfully": "Webové soubory byly úspěšně přidány.", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "Stačí popsat, co potřebujete, a umělá inteligence za vás vytvoří kód v jazyce HTML, CSS, JS nebo Liquid.", "You are editing a live, public site ": "Upravujete živý veřejný web ", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "Musí být nainstalována sada dotnet sdk 6.0 nebo vyšší.", "{0} created!/{0} will be replaced by the entity type.": "{0} bylo vytvořeno." diff --git a/l10n/bundle.l10n.de.json b/l10n/bundle.l10n.de.json index ae86e840..e83fa1f6 100644 --- a/l10n/bundle.l10n.de.json +++ b/l10n/bundle.l10n.de.json @@ -67,7 +67,6 @@ "Saving your file ...": "Datei wird gespeichert ...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Ordner für neues PCF-Steuerelement auswählen", "Select Type": "Typ auswählen", - "Selection is empty!": "Auswahl ist leer!", "The Power Pages generator is ready for use in your VS Code extension!": "Der Power Pages-Generator ist in Ihrer VS Code-Erweiterung zur Verwendung bereit!", "The name you want to give to this authentication profile": "Der Name, den Sie diesem Authentifizierungsprofil geben möchten", "The pac CLI is ready for use in your VS Code terminal!": "Die pac CLI ist in Ihrem VS-Code-Terminal zur Verwendung bereit!", diff --git a/l10n/bundle.l10n.es.json b/l10n/bundle.l10n.es.json index faf3e39d..dd20866c 100644 --- a/l10n/bundle.l10n.es.json +++ b/l10n/bundle.l10n.es.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "Copiado al portapapeles.", "Creating {0}.../{0} will be replaced by the entity type.": "Creando {0}...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "Nombre para mostrar: {0}\nNombre único: {1}\nVersión: {2}\nTipo: {3}", + "Do not show again": "No volver a mostrar", "Edit the site": "Editar el sitio", "Enter Organization ID": "Escriba el id. de organización", "Enter name": "Escribir nombre", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "Se puede hacer referencia al archivo por su nombre {0} aquí.", "File(s) already exist. No new files to add": "Los archivos ya existen. No hay archivos nuevos que agregar", "Installing Power Pages generator(v{0}).../{0} represents the version number": "Instalando el generador de Power Pages (v{0})...", + "Learn more about Copilot": "Más información sobre Copilot", + "Let Copilot help you code": "Deje que Copilot le ayude a programar", "Managed": "Administrado", "Maximum 30 characters allowed": "Se permite un máximo de 30 caracteres", "Microsoft wants your feeback": "Microsoft quiere conocer sus comentarios", @@ -67,13 +70,14 @@ "Saving your file ...": "Guardando su archivo...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Seleccione Carpeta para el nuevo control PCF", "Select Type": "Seleccionar tipo", - "Selection is empty!": "La selección está vacía.", + "Selection is empty.": "La selección está vacía.", "The Power Pages generator is ready for use in your VS Code extension!": "¡El generador de Power Pages ya se puede usar en su extensión de VS Code!", "The name you want to give to this authentication profile": "Nombre que desea asignar a este perfil de autenticación", "The pac CLI is ready for use in your VS Code terminal!": "¡pac CLI ya se puede usar en su terminal de VS Code!", "There was a permissions problem with the server": "Se produjo un problema de permisos con el servidor", "There was a problem opening the workspace": "Hubo un problema al abrir el área de trabajo", "There’s a problem on the back end": "Hay un problema en el back-end", + "Try Copilot for Power Pages": "Pruebe Copilot para Power Pages", "Try again": "Volver a intentarlo", "Unable to complete the request": "No se puede completar la solicitud", "Unable to find that app": "No se puede encontrar esa aplicación", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "Se detectó un error al preparar el archivo para su edición.", "Web files": "Archivos web", "Webfile(s) added successfully": "Sitios web agregados correctamente", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "Ya se trate de código HTML, CSS, JS o Liquid, describa lo que necesita y deje que la IA lo cree por usted.", "You are editing a live, public site ": "Está editando un sitio público activo ", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "debe estar instalado dotnet sdk 6.0 o una versión posterior", "{0} created!/{0} will be replaced by the entity type.": "Se ha creado {0}." diff --git a/l10n/bundle.l10n.fr.json b/l10n/bundle.l10n.fr.json index bcd1f7ea..0d71bc4d 100644 --- a/l10n/bundle.l10n.fr.json +++ b/l10n/bundle.l10n.fr.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "Copié dans le Presse-papiers !", "Creating {0}.../{0} will be replaced by the entity type.": "Création de {0}...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "Nom complet : {0}\nName unique : {1}\nVersion : {2}\nType : {3}", + "Do not show again": "Ne plus afficher", "Edit the site": "Modifier le site", "Enter Organization ID": "Entrer l’ID d’organisation", "Enter name": "Entrer un nom", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "Il se peut que le fichier soit référencé sous le nom {0} ici.", "File(s) already exist. No new files to add": "Le ou les fichiers existent déjà. Aucun nouveau fichier à ajouter", "Installing Power Pages generator(v{0}).../{0} represents the version number": "Installation du générateur Power Pages (v{0})...", + "Learn more about Copilot": "En savoir plus sur Copilot", + "Let Copilot help you code": "Laissez Copilot vous aider à coder", "Managed": "Géré", "Maximum 30 characters allowed": "30 caractères maximum autorisés", "Microsoft wants your feeback": "Microsoft souhaite recevoir vos commentaires", @@ -67,13 +70,14 @@ "Saving your file ...": "Enregistrement de votre fichier en cours...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Sélectionner un dossier pour le nouveau contrôle PCF", "Select Type": "Sélectionner le type", - "Selection is empty!": "La sélection est vide !", + "Selection is empty.": "La sélection est vide.", "The Power Pages generator is ready for use in your VS Code extension!": "Le générateur Power Pages est prêt à être utilisé dans votre extension VS Code !", "The name you want to give to this authentication profile": "Nom que vous souhaitez attribuer à ce profil d’authentification", "The pac CLI is ready for use in your VS Code terminal!": "Le pac CLI est prêt à être utilisé dans votre terminal VS Code !", "There was a permissions problem with the server": "Problème d’autorisations avec le serveur", "There was a problem opening the workspace": "Un problème s’est produit lors de l’ouverture de l’espace de travail", "There’s a problem on the back end": "Problème dans le back end", + "Try Copilot for Power Pages": "Essayer Copilot pour Power Pages", "Try again": "Réessayer", "Unable to complete the request": "Impossible de traiter la demande", "Unable to find that app": "Impossible de trouver cette application", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "Une erreur s’est produite lors de la préparation du fichier pour la modification.", "Web files": "Fichiers web", "Webfile(s) added successfully": "Le ou les fichiers web ont été ajoutés", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "Que ce soit du code HTML, CSS, JS ou Liquid, décrivez simplement ce dont vous avez besoin et laissez l’IA le créer pour vous.", "You are editing a live, public site ": "Vous modifiez un site public actif ", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "la version du kit de développement logiciel (sdk) dotnet sdk 6.0 ou supérieure doit être installé", "{0} created!/{0} will be replaced by the entity type.": "{0} créé !" diff --git a/l10n/bundle.l10n.it.json b/l10n/bundle.l10n.it.json index d8b0477e..8b0d9ac3 100644 --- a/l10n/bundle.l10n.it.json +++ b/l10n/bundle.l10n.it.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "Copiato negli Appunti.", "Creating {0}.../{0} will be replaced by the entity type.": "Creazione di {0} in corso...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "Nome visualizzato: {0}\nNome univoco: {1}\nVersione: {2}\nTipo: {3}", + "Do not show again": "Non visualizzare più", "Edit the site": "Modifica il sito", "Enter Organization ID": "Immetti ID organizzazione", "Enter name": "Immetti nome", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "È possibile che qui si faccia riferimento al file con il nome {0}.", "File(s) already exist. No new files to add": "I file esistono già. Nessun nuovo file da aggiungere", "Installing Power Pages generator(v{0}).../{0} represents the version number": "Installazione del generatore di Power Pages in corso(v{0})...", + "Learn more about Copilot": "Scopri di più su Copilot", + "Let Copilot help you code": "Consenti a Copilot di aiutarti con il codice", "Managed": "Gestita", "Maximum 30 characters allowed": "È consentito un massimo di 30 caratteri", "Microsoft wants your feeback": "Microsoft desidera ricevere il tuo feedback", @@ -67,13 +70,14 @@ "Saving your file ...": "Salvataggio del file in corso...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Seleziona cartella per nuovo controllo PCF", "Select Type": "Seleziona tipo", - "Selection is empty!": "Selezione vuota.", + "Selection is empty.": "Selezione vuota.", "The Power Pages generator is ready for use in your VS Code extension!": "Il generatore Power Pages è pronto per l'utilizzo nell'estensione VS Code.", "The name you want to give to this authentication profile": "Il nome che vuoi assegnare a questo profilo di autenticazione", "The pac CLI is ready for use in your VS Code terminal!": "pac CLI è pronto per l'utilizzo nel terminale VS Code.", "There was a permissions problem with the server": "Si è verificato un problema di autorizzazione con il server", "There was a problem opening the workspace": "Si è verificato un problema durante l'apertura dell'area di lavoro", "There’s a problem on the back end": "C'è™un problema nel back-end", + "Try Copilot for Power Pages": "Prova Copilot per Power Pages", "Try again": "Riprova", "Unable to complete the request": "Impossibile completare la richiesta", "Unable to find that app": "Impossibile trovare tale app", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "Si è verificato un errore durante la preparazione del file per la modifica.", "Web files": "File Web", "Webfile(s) added successfully": "Aggiunta file Web completata", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "Che si tratti di codice HTML, CSS, JS o Liquid, descrivi ciò che ti serve e lascia che se ne occupi l'intelligenza artificiale.", "You are editing a live, public site ": "Stai modificando un sito pubblico live ", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "È necessario installare dotnet SDK 6.0 o versione successiva", "{0} created!/{0} will be replaced by the entity type.": "{0} creato." diff --git a/l10n/bundle.l10n.ja.json b/l10n/bundle.l10n.ja.json index 9928d035..e6e16909 100644 --- a/l10n/bundle.l10n.ja.json +++ b/l10n/bundle.l10n.ja.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "クリップボードにコピーしました!", "Creating {0}.../{0} will be replaced by the entity type.": "{0} を作成しています...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "表示名: {0}\n一意の名前: {1}\nバージョン: {2}\n種類: {3}", + "Do not show again": "次回から表示しない", "Edit the site": "サイトの編集", "Enter Organization ID": "組織 ID を入力します", "Enter name": "名前を入力します", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "ここではファイルが {0} の名前で参照されている可能性があります。", "File(s) already exist. No new files to add": "ファイルは既に存在します。追加する新しいファイルはありません", "Installing Power Pages generator(v{0}).../{0} represents the version number": "Power Pages ジェネレーター (v{0}) をインストールしています...", + "Learn more about Copilot": "Copilot の詳細情報", + "Let Copilot help you code": "Copilot でコーディングをサポートします", "Managed": "マネージド", "Maximum 30 characters allowed": "最大 30 文字使用可能", "Microsoft wants your feeback": "Microsoft にご意見をお寄せください", @@ -67,13 +70,14 @@ "Saving your file ...": "ファイルを保存しています...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "新しい PCF コントロールのフォルダーを選択する", "Select Type": "種類の選択", - "Selection is empty!": "選択内容が空です!", + "Selection is empty.": "選択内容が空です。", "The Power Pages generator is ready for use in your VS Code extension!": "Power Pages ジェネレーターは、VS Code 拡張機能での使用準備が完了しています。", "The name you want to give to this authentication profile": "この認証プロファイルに指定する名前", "The pac CLI is ready for use in your VS Code terminal!": "pac CLI は、VS Code ターミナルでの使用準備が完了しています。", "There was a permissions problem with the server": "サーバーのアクセス許可に関する問題が発生しました", "There was a problem opening the workspace": "ワークスペースを開くときに問題が発生しました", "There’s a problem on the back end": "バック エンドに問題があります", + "Try Copilot for Power Pages": "Power Pages の Copilot を試す", "Try again": "やり直す", "Unable to complete the request": "要求を完了できません", "Unable to find that app": "そのアプリが見つかりません", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "ファイルの編集準備中にエラーが発生しました。", "Web files": "Web ファイル", "Webfile(s) added successfully": "正常に追加された Web ファイルの数", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "HTML、CSS、JS、または Liquid コードのいずれであっても、必要事項を説明して、AI にコードを構築させましょう。", "You are editing a live, public site ": "ライブのパブリック サイトを編集中です ", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "DotNet SDK 6.0 以上を必ずインストールしてください", "{0} created!/{0} will be replaced by the entity type.": "{0} が作成されました。" diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index df27d017..d4283a38 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -73,9 +73,6 @@ "{0} represents the name of the file" ] }, - "Enter the name of the web template": "Enter the name of the web template", - "Please enter a name for the web template.": "Please enter a name for the web template.", - "A webtemplate with the same name already exists. Please enter a different name.": "A webtemplate with the same name already exists. Please enter a different name.", "No page templates found": "No page templates found", "No webpages found": "No webpages found", "New Webpage": "New Webpage", @@ -88,6 +85,9 @@ "File(s) already exist. No new files to add": "File(s) already exist. No new files to add", "Web files": "Web files", "Webfile(s) added successfully": "Webfile(s) added successfully", + "Enter the name of the web template": "Enter the name of the web template", + "Please enter a name for the web template.": "Please enter a name for the web template.", + "A webtemplate with the same name already exists. Please enter a different name.": "A webtemplate with the same name already exists. Please enter a different name.", "Page Template name cannot be empty.": "Page Template name cannot be empty.", "New Page Template": "New Page Template", "Choose web template": "Choose web template", diff --git a/l10n/bundle.l10n.ko.json b/l10n/bundle.l10n.ko.json index f4097e12..f8a13d69 100644 --- a/l10n/bundle.l10n.ko.json +++ b/l10n/bundle.l10n.ko.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "클립보드에 복사되었습니다.", "Creating {0}.../{0} will be replaced by the entity type.": "{0} 생성 중...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "표시 이름: {0}\n고유 이름: {1}\n버전: {2}\n유형: {3}", + "Do not show again": "다시 표시 안 함", "Edit the site": "사이트 편집", "Enter Organization ID": "조직 ID 입력", "Enter name": "이름 입력", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "이름({0})으로 여기에서 파일을 참조할 수 있습니다.", "File(s) already exist. No new files to add": "파일이 이미 있습니다. 추가할 새 파일이 없습니다.", "Installing Power Pages generator(v{0}).../{0} represents the version number": "Power Pages 생성기(v{0}) 설치 중...", + "Learn more about Copilot": "Copilot에 대해 자세히 알아보기", + "Let Copilot help you code": "코딩에 Copilot 도움받기", "Managed": "관리형", "Maximum 30 characters allowed": "최대 30자까지 허용됩니다.", "Microsoft wants your feeback": "Microsoft는 여러분의 피드백을 기다립니다.", @@ -67,13 +70,14 @@ "Saving your file ...": "파일을 저장하는 중...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "새 PCF 컨트롤에 대한 폴더 선택", "Select Type": "유형 선택", - "Selection is empty!": "선택이 비어 있습니다.", + "Selection is empty.": "선택이 비어 있습니다.", "The Power Pages generator is ready for use in your VS Code extension!": "VS Code 확장에서 Power Pages 생성기를 사용할 준비가 되었습니다.", "The name you want to give to this authentication profile": "이 인증 프로필에 지정할 이름", "The pac CLI is ready for use in your VS Code terminal!": "pac CLI를 VS 코드 터미널에 사용할 준비가 완료되었습니다!", "There was a permissions problem with the server": "서버에 권한 문제가 있습니다.", "There was a problem opening the workspace": "작업 영역을 여는 중 문제가 발생했습니다", "There’s a problem on the back end": "백 엔드에 문제가 있습니다", + "Try Copilot for Power Pages": "Power Pages에 Copilot 사용해 보기", "Try again": "다시 시도", "Unable to complete the request": "요청을 완료할 수 없음", "Unable to find that app": "해당 앱을 찾을 수 없음", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "파일 편집을 준비하는 동안 오류가 발생했습니다.", "Web files": "웹 파일", "Webfile(s) added successfully": "웹 파일 추가됨", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "HTML, CSS, JS, Liquid 등 필요한 코드를 설명하면 AI가 대신 만들어줍니다.", "You are editing a live, public site ": "라이브 상태인 공개 사이트를 편집하고 있습니다. ", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "dotnet sdk 6.0 이상을 설치해야 합니다", "{0} created!/{0} will be replaced by the entity type.": "{0} 생성 완료!" diff --git a/l10n/bundle.l10n.pt-br.json b/l10n/bundle.l10n.pt-br.json index 00e0057a..38e2b454 100644 --- a/l10n/bundle.l10n.pt-br.json +++ b/l10n/bundle.l10n.pt-br.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "Copiado na área de transferência!", "Creating {0}.../{0} will be replaced by the entity type.": "Criando {0}...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "Nome de Exibição: {0}\nNome Exclusivo: {1}\nVersão: {2}\nTipo: {3}", + "Do not show again": "Não mostrar novamente", "Edit the site": "Editar o site", "Enter Organization ID": "Inserir ID da Organização", "Enter name": "Inserir nome", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "O arquivo pode ser referenciado pelo nome {0} aqui.", "File(s) already exist. No new files to add": "Os arquivos já existem. Nenhum novo arquivo a adicionar", "Installing Power Pages generator(v{0}).../{0} represents the version number": "Instalando o gerador do Power Pages(v{0})...", + "Learn more about Copilot": "Saiba mais sobre o Copilot", + "Let Copilot help you code": "Permitir que o Copilot ajude você a codificar", "Managed": "Gerenciado(a)", "Maximum 30 characters allowed": "O máximo permitido são 30 caracteres", "Microsoft wants your feeback": "A Microsoft quer saber sua opinião", @@ -67,13 +70,14 @@ "Saving your file ...": "Salvando seu arquivo...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Selecionar Pasta para novo Controle PCF", "Select Type": "Selecionar Tipo", - "Selection is empty!": "A seleção está vazia!", + "Selection is empty.": "A seleção está vazia.", "The Power Pages generator is ready for use in your VS Code extension!": "O gerador do Power Pages está pronto para uso na sua extensão do VS Code.", "The name you want to give to this authentication profile": "O nome que você deseja dar a este perfil de autenticação", "The pac CLI is ready for use in your VS Code terminal!": "A pac CLI está pronta para uso no seu terminal do VS Code!", "There was a permissions problem with the server": "Houve um problema de permissões com o servidor", "There was a problem opening the workspace": "Houve um problema ao abrir o espaço de trabalho", "There’s a problem on the back end": "Há um problema no back-end", + "Try Copilot for Power Pages": "Testar o Copilot para Power Pages", "Try again": "Tentar novamente", "Unable to complete the request": "Não foi possível concluir a solicitação", "Unable to find that app": "Não é possível encontrar esse aplicativo", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "Encontramos um erro ao preparar o arquivo para edição.", "Web files": "Arquivos da Web", "Webfile(s) added successfully": "Arquivos da Web adicionados com êxito", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "Seja código HTML, CSS, JS ou Liquid, descreva apenas o que é necessário e deixe a IA criá-lo para você.", "You are editing a live, public site ": "Você está editando um site público ativo ", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "O dotnet sdk 6.0 ou superior deve estar instalado", "{0} created!/{0} will be replaced by the entity type.": "{0} criado." diff --git a/l10n/bundle.l10n.ru.json b/l10n/bundle.l10n.ru.json index 2c6beaf2..1b6ee6b9 100644 --- a/l10n/bundle.l10n.ru.json +++ b/l10n/bundle.l10n.ru.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "Скопировано в буфер обмена.", "Creating {0}.../{0} will be replaced by the entity type.": "Создание {0}...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "Отображаемая имя: {0}\nУникальное имя: {1}\nВерсия: {2}\nТип: {3}", + "Do not show again": "Больше не показывать", "Edit the site": "Изменить сайт", "Enter Organization ID": "Введите ИД организации", "Enter name": "Введите имя", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "Здесь файл может быть задан именем {0}.", "File(s) already exist. No new files to add": "Файлы уже существуют. Нет новых файлов для добавления", "Installing Power Pages generator(v{0}).../{0} represents the version number": "Установка генератора Power Pages (v{0})...", + "Learn more about Copilot": "Подробнее о Copilot", + "Let Copilot help you code": "Copilot поможет вам писать код", "Managed": "Управляемые", "Maximum 30 characters allowed": "Допускается не более 30 знаков", "Microsoft wants your feeback": "Корпорацию Майкрософт интересует ваше мнение", @@ -67,13 +70,14 @@ "Saving your file ...": "Сохранение файла...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Выберите папку для нового элемента управления PCF", "Select Type": "Выбрать тип", - "Selection is empty!": "Ничего не выбрано.", + "Selection is empty.": "Ничего не выбрано.", "The Power Pages generator is ready for use in your VS Code extension!": "Генератор Power Pages готов к использованию в расширении VS Code.", "The name you want to give to this authentication profile": "Имя, которое вы хотите дать этому профилю проверки подлинности", "The pac CLI is ready for use in your VS Code terminal!": "Компонент pac CLI готов к использованию в терминале VS Code!", "There was a permissions problem with the server": "Возникла проблема с разрешениями на сервере", "There was a problem opening the workspace": "При открытии рабочей области возникла проблема", "There’s a problem on the back end": "Возникла проблема на сервере", + "Try Copilot for Power Pages": "Попробуйте Copilot для Power Pages", "Try again": "Повторить попытку", "Unable to complete the request": "Не удается завершить запрос", "Unable to find that app": "Не удалось найти приложение", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "Произошла ошибка при подготовке файла к редактированию.", "Web files": "Веб-файлы", "Webfile(s) added successfully": "Веб-файлы успешно добавлены", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "Неважно, создаете вы код HTML, CSS, JS или Liquid, просто опишите свою задачу и ИИ сгенерирует код за вас.", "You are editing a live, public site ": "Вы редактируете опубликованный открытый сайт ", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "Необходимо установить версию dotnet sdk 6.0 или более позднюю", "{0} created!/{0} will be replaced by the entity type.": "Создано: {0}." diff --git a/l10n/bundle.l10n.tr.json b/l10n/bundle.l10n.tr.json index 227a93b0..fe2615bf 100644 --- a/l10n/bundle.l10n.tr.json +++ b/l10n/bundle.l10n.tr.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "Panoya kopyalandı!", "Creating {0}.../{0} will be replaced by the entity type.": "{0} oluşturuluyor...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "Görünen Ad: {0}\nBenzersiz Ad: {1}\nSürüm: {2}\nTür: {3}", + "Do not show again": "Bir daha gösterme", "Edit the site": "Siteyi düzenle", "Enter Organization ID": "Kuruluş Kimliğini Girin", "Enter name": "Ad girin", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "Dosyaya burada {0} adıyla başvurulmuş olabilir.", "File(s) already exist. No new files to add": "Dosyalar zaten mevcut. Eklenecek yeni dosya yok.", "Installing Power Pages generator(v{0}).../{0} represents the version number": "Power Pages oluşturucusu (v{0}) yükleniyor...", + "Learn more about Copilot": "Copilot hakkında daha fazla bilgi edinin", + "Let Copilot help you code": "Copilot kodlamanıza yardımcı olsun", "Managed": "Yönetilen", "Maximum 30 characters allowed": "En fazla 30 karaktere izin verilir", "Microsoft wants your feeback": "Microsoft geri bildiriminizi almak istiyor", @@ -67,13 +70,14 @@ "Saving your file ...": "Dosyanız kaydediliyor...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Yeni PCF Denetimi için Klasör Seçin", "Select Type": "Tür Seç", - "Selection is empty!": "Seçim boş!", + "Selection is empty.": "Seçim boş.", "The Power Pages generator is ready for use in your VS Code extension!": "Power Pages oluşturucusu, VS Code uzantınızda kullanıma hazır!", "The name you want to give to this authentication profile": "Bu kimlik doğrulama profiline vermek istediğiniz ad", "The pac CLI is ready for use in your VS Code terminal!": "pac CLI, VS Kod terminalinizde kullanıma hazır!", "There was a permissions problem with the server": "Sunucuyla ilgili bir izin sorunu yaşandı", "There was a problem opening the workspace": "Çalışma alanı açılırken bir sorun oluştu", "There’s a problem on the back end": "Arka uçta bir sorun oluştu", + "Try Copilot for Power Pages": "Power Pages için Copilot'ı deneyin", "Try again": "Yeniden dene", "Unable to complete the request": "İstek tamamlanamadı", "Unable to find that app": "Bu uygulama bulunamadı", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "Dosya düzenlemeye hazırlanırken bir hatayla karşılaştık.", "Web files": "Web dosyaları", "Webfile(s) added successfully": "Web dosyaları başarıyla eklendi", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "İster HTML, CSS, JS ister Liquid kodu olsun neye ihtiyacınız olduğunu açıklamanız yeterlidir; AI bunu sizin için oluşturur.", "You are editing a live, public site ": "Yayında olan, herkese açık bir siteyi düzenliyorsunuz ", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "dotnet sdk 6.0 veya üstü bir sürüm yüklenmelidir", "{0} created!/{0} will be replaced by the entity type.": "{0} oluşturuldu!" diff --git a/l10n/bundle.l10n.zh-cn.json b/l10n/bundle.l10n.zh-cn.json index 9abbd999..c0dba5e7 100644 --- a/l10n/bundle.l10n.zh-cn.json +++ b/l10n/bundle.l10n.zh-cn.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "已复制到剪贴板!", "Creating {0}.../{0} will be replaced by the entity type.": "正在创建 {0}...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "显示名称: {0}\n唯一名称: {1}\n版本: {2}\n类型: {3}", + "Do not show again": "不再显示", "Edit the site": "编辑站点", "Enter Organization ID": "输入组织 ID", "Enter name": "输入名称", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "文件可能由此处的名称 {0} 引用。", "File(s) already exist. No new files to add": "文件已存在。没有要添加的新文件", "Installing Power Pages generator(v{0}).../{0} represents the version number": "正在安装 Power Pages 生成器(v{0})...", + "Learn more about Copilot": "详细了解 Copilot", + "Let Copilot help you code": "让 Copilot 帮助您编码", "Managed": "托管", "Maximum 30 characters allowed": "最多允许 30 个字符", "Microsoft wants your feeback": "Microsoft 需要您的反馈", @@ -67,13 +70,14 @@ "Saving your file ...": "正在保存文件...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "为新的 PCF 控件选择文件夹", "Select Type": "选择类型", - "Selection is empty!": "选择是空的!", + "Selection is empty.": "选择是空的。", "The Power Pages generator is ready for use in your VS Code extension!": "Power Pages 生成器已可供在 VS Code 扩展中使用!", "The name you want to give to this authentication profile": "要为此身份验证配置文件指定的名称", "The pac CLI is ready for use in your VS Code terminal!": "pac CLI 已可供在 VS Code 终端中使用!", "There was a permissions problem with the server": "存在服务器权限问题", "There was a problem opening the workspace": "打开工作区时出现问题", "There’s a problem on the back end": "后端出现问题", + "Try Copilot for Power Pages": "尝试将 Copilot 用于 Power Pages", "Try again": "重试", "Unable to complete the request": "无法完成请求", "Unable to find that app": "找不到该应用", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "准备可供编辑的文件时出错。", "Web files": "Web 文件", "Webfile(s) added successfully": "已成功添加 Web 文件", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "无论是 HTML、CSS、JS 还是 Liquid 代码,只要描述您需要什么,让 AI 为您构建。", "You are editing a live, public site ": "您正在编辑一个公共活动站点", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "必须安装 DotNet SDK 6.0 或更高版本", "{0} created!/{0} will be replaced by the entity type.": "{0} 已创建!" diff --git a/l10n/bundle.l10n.zh-tw.json b/l10n/bundle.l10n.zh-tw.json index aa36414d..061b6db7 100644 --- a/l10n/bundle.l10n.zh-tw.json +++ b/l10n/bundle.l10n.zh-tw.json @@ -21,6 +21,7 @@ "Copied to clipboard!": "已複製至剪貼簿!", "Creating {0}.../{0} will be replaced by the entity type.": "正在建立 {0}...", "Display Name: {0}\nUnique Name: {1}\nVersion: {2}\nType: {3}/This is a multi-line tooltipThe {0} represents Solution's Friendly / Display nameThe {1} represents Solution's unique nameThe {2} represents Solution's Version numberThe {3} represents Solution's Type (Managed or Unmanaged), but that test is localized separately.": "顯示名稱: {0}\n唯一名稱: {1}\n版本: {2}\n類型: {3}", + "Do not show again": "不要再顯示", "Edit the site": "編輯網站", "Enter Organization ID": "輸入組織識別碼", "Enter name": "輸入名稱", @@ -35,6 +36,8 @@ "File might be referenced by name {0} here./{0} represents the name of the file": "此處可能以名稱 {0} 參考檔案。", "File(s) already exist. No new files to add": "檔案已經存在。沒有可新增的新檔案", "Installing Power Pages generator(v{0}).../{0} represents the version number": "正在安裝 Power Pages 產生器 (v{0})...", + "Learn more about Copilot": "深入了解 Copilot", + "Let Copilot help you code": "讓 Copilot 協助您撰寫程式碼", "Managed": "受控", "Maximum 30 characters allowed": "最多允許 30 個字元", "Microsoft wants your feeback": "Microsoft 想知道您的意見反應", @@ -67,13 +70,14 @@ "Saving your file ...": "正在儲存您的檔案...", "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "為新的 PCF 控制項選取資料夾", "Select Type": "選取類型", - "Selection is empty!": "選取項目是空的!", + "Selection is empty.": "選取項目是空的。", "The Power Pages generator is ready for use in your VS Code extension!": "您已可在 VS Code 擴充功能中使用 Power Pages 產生器!", "The name you want to give to this authentication profile": "您要為此驗證設定檔指定的名稱", "The pac CLI is ready for use in your VS Code terminal!": "您已可在 VS Code 終端中使用 pac CLI!", "There was a permissions problem with the server": "伺服器發生權限問題", "There was a problem opening the workspace": "開啟工作區時發生問題", "There’s a problem on the back end": "後端發生問題", + "Try Copilot for Power Pages": "試用 Power Pages 的 Copilot", "Try again": "再試一次", "Unable to complete the request": "無法完成要求", "Unable to find that app": "找不到該應用程式", @@ -82,6 +86,7 @@ "We encountered an error preparing the file for edit.": "準備要編輯的檔案時發生錯誤。", "Web files": "Web 檔案", "Webfile(s) added successfully": "已成功新增 Web 檔案", + "Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you.": "無論是 HTML、CSS、JS 或 Liquid 程式碼,只要描述您的需求,就可以交由 AI 為您建置。", "You are editing a live, public site ": "您正在編輯已上線、公開的網站", "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": "必須安裝 DotNet SDK 6.0 或更新版本", "{0} created!/{0} will be replaced by the entity type.": "{0} 已建立!" diff --git a/loc/translations-import/vscode-powerplatform.cs.xlf b/loc/translations-import/vscode-powerplatform.cs.xlf index 5311565f..9d7a5b3d 100644 --- a/loc/translations-import/vscode-powerplatform.cs.xlf +++ b/loc/translations-import/vscode-powerplatform.cs.xlf @@ -106,6 +106,10 @@ Jedinečné jméno: {1} Verze: {2} Typ: {3} + + Do not show again + Příště nezobrazovat + Edit the site Upravit web @@ -165,6 +169,14 @@ Typ: {3} {0} represents the version number Instaluje se generátor Power Pages (verze {0})... + + Learn more about Copilot + Další informace o Copilotovi + + + Let Copilot help you code + Copilot vám pomůže s kódem + Managed Spravováno @@ -310,8 +322,8 @@ Kód organizace: {3} Select Type Vybrat typ - - Selection is empty! + + Selection is empty. Výběr je prázdný. @@ -338,6 +350,10 @@ Kód organizace: {3} There’s a problem on the back end U back-endu se vyskytl problém. + + Try Copilot for Power Pages + Vyzkoušejte Copilota pro Power Pages + Try again Zkusit znovu @@ -371,6 +387,10 @@ Kód organizace: {3} Webfile(s) added successfully Webové soubory byly úspěšně přidány. + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + Stačí popsat, co potřebujete, a umělá inteligence za vás vytvoří kód v jazyce HTML, CSS, JS nebo Liquid. + You are editing a live, public site Upravujete živý veřejný web diff --git a/loc/translations-import/vscode-powerplatform.de.xlf b/loc/translations-import/vscode-powerplatform.de.xlf index 8b5195ba..36e89770 100644 --- a/loc/translations-import/vscode-powerplatform.de.xlf +++ b/loc/translations-import/vscode-powerplatform.de.xlf @@ -106,6 +106,9 @@ Eindeutiger Name: {1} Version: {2} Typ: {3} + + Do not show again + Edit the site Website bearbeiten @@ -165,6 +168,12 @@ Typ: {3} {0} represents the version number Power Pages Generator(v{0}) wird installiert ... + + Learn more about Copilot + + + Let Copilot help you code + Managed Verwaltet @@ -310,9 +319,8 @@ Organisations-ID: {3} Select Type Typ auswählen - - Selection is empty! - Auswahl ist leer! + + Selection is empty. The Power Pages generator is ready for use in your VS Code extension! @@ -338,6 +346,9 @@ Organisations-ID: {3} There’s a problem on the back end Problem mit dem Backend + + Try Copilot for Power Pages + Try again Wiederholen @@ -371,6 +382,9 @@ Organisations-ID: {3} Webfile(s) added successfully Webdateien wurden erfolgreich hinzugefügt. + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + You are editing a live, public site Sie bearbeiten eine öffentliche Live-Website diff --git a/loc/translations-import/vscode-powerplatform.es.xlf b/loc/translations-import/vscode-powerplatform.es.xlf index d01ba43a..494b7919 100644 --- a/loc/translations-import/vscode-powerplatform.es.xlf +++ b/loc/translations-import/vscode-powerplatform.es.xlf @@ -106,6 +106,10 @@ Nombre único: {1} Versión: {2} Tipo: {3} + + Do not show again + No volver a mostrar + Edit the site Editar el sitio @@ -165,6 +169,14 @@ Tipo: {3} {0} represents the version number Instalando el generador de Power Pages (v{0})... + + Learn more about Copilot + Más información sobre Copilot + + + Let Copilot help you code + Deje que Copilot le ayude a programar + Managed Administrado @@ -310,8 +322,8 @@ Id. de organización: {3} Select Type Seleccionar tipo - - Selection is empty! + + Selection is empty. La selección está vacía. @@ -338,6 +350,10 @@ Id. de organización: {3} There’s a problem on the back end Hay un problema en el back-end + + Try Copilot for Power Pages + Pruebe Copilot para Power Pages + Try again Volver a intentarlo @@ -371,6 +387,10 @@ Id. de organización: {3} Webfile(s) added successfully Sitios web agregados correctamente + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + Ya se trate de código HTML, CSS, JS o Liquid, describa lo que necesita y deje que la IA lo cree por usted. + You are editing a live, public site Está editando un sitio público activo diff --git a/loc/translations-import/vscode-powerplatform.fr.xlf b/loc/translations-import/vscode-powerplatform.fr.xlf index 6f8a4b12..fab25d91 100644 --- a/loc/translations-import/vscode-powerplatform.fr.xlf +++ b/loc/translations-import/vscode-powerplatform.fr.xlf @@ -106,6 +106,10 @@ Name unique : {1} Version : {2} Type : {3} + + Do not show again + Ne plus afficher + Edit the site Modifier le site @@ -165,6 +169,14 @@ Type : {3} {0} represents the version number Installation du générateur Power Pages (v{0})... + + Learn more about Copilot + En savoir plus sur Copilot + + + Let Copilot help you code + Laissez Copilot vous aider à coder + Managed Géré @@ -310,9 +322,9 @@ ID d’organisation : {3} Select Type Sélectionner le type - - Selection is empty! - La sélection est vide ! + + Selection is empty. + La sélection est vide. The Power Pages generator is ready for use in your VS Code extension! @@ -338,6 +350,10 @@ ID d’organisation : {3} There’s a problem on the back end Problème dans le back end + + Try Copilot for Power Pages + Essayer Copilot pour Power Pages + Try again Réessayer @@ -371,6 +387,10 @@ ID d’organisation : {3} Webfile(s) added successfully Le ou les fichiers web ont été ajoutés + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + Que ce soit du code HTML, CSS, JS ou Liquid, décrivez simplement ce dont vous avez besoin et laissez l’IA le créer pour vous. + You are editing a live, public site Vous modifiez un site public actif diff --git a/loc/translations-import/vscode-powerplatform.it.xlf b/loc/translations-import/vscode-powerplatform.it.xlf index 6a764769..b48ff89b 100644 --- a/loc/translations-import/vscode-powerplatform.it.xlf +++ b/loc/translations-import/vscode-powerplatform.it.xlf @@ -106,6 +106,10 @@ Nome univoco: {1} Versione: {2} Tipo: {3} + + Do not show again + Non visualizzare più + Edit the site Modifica il sito @@ -165,6 +169,14 @@ Tipo: {3} {0} represents the version number Installazione del generatore di Power Pages in corso(v{0})... + + Learn more about Copilot + Scopri di più su Copilot + + + Let Copilot help you code + Consenti a Copilot di aiutarti con il codice + Managed Gestita @@ -310,8 +322,8 @@ ID organizzazione: {3} Select Type Seleziona tipo - - Selection is empty! + + Selection is empty. Selezione vuota. @@ -338,6 +350,10 @@ ID organizzazione: {3} There’s a problem on the back end C'è™un problema nel back-end + + Try Copilot for Power Pages + Prova Copilot per Power Pages + Try again Riprova @@ -371,6 +387,10 @@ ID organizzazione: {3} Webfile(s) added successfully Aggiunta file Web completata + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + Che si tratti di codice HTML, CSS, JS o Liquid, descrivi ciò che ti serve e lascia che se ne occupi l'intelligenza artificiale. + You are editing a live, public site Stai modificando un sito pubblico live diff --git a/loc/translations-import/vscode-powerplatform.ja.xlf b/loc/translations-import/vscode-powerplatform.ja.xlf index b72e44e6..3e642e0c 100644 --- a/loc/translations-import/vscode-powerplatform.ja.xlf +++ b/loc/translations-import/vscode-powerplatform.ja.xlf @@ -106,6 +106,10 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca バージョン: {2} 種類: {3} + + Do not show again + 次回から表示しない + Edit the site サイトの編集 @@ -165,6 +169,14 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca {0} represents the version number Power Pages ジェネレーター (v{0}) をインストールしています... + + Learn more about Copilot + Copilot の詳細情報 + + + Let Copilot help you code + Copilot でコーディングをサポートします + Managed マネージド @@ -310,9 +322,9 @@ URL: {1} Select Type 種類の選択 - - Selection is empty! - 選択内容が空です! + + Selection is empty. + 選択内容が空です。 The Power Pages generator is ready for use in your VS Code extension! @@ -338,6 +350,10 @@ URL: {1} There’s a problem on the back end バック エンドに問題があります + + Try Copilot for Power Pages + Power Pages の Copilot を試す + Try again やり直す @@ -371,6 +387,10 @@ URL: {1} Webfile(s) added successfully 正常に追加された Web ファイルの数 + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + HTML、CSS、JS、または Liquid コードのいずれであっても、必要事項を説明して、AI にコードを構築させましょう。 + You are editing a live, public site ライブのパブリック サイトを編集中です diff --git a/loc/translations-import/vscode-powerplatform.ko.xlf b/loc/translations-import/vscode-powerplatform.ko.xlf index d3bdd2d0..bd0e4ff0 100644 --- a/loc/translations-import/vscode-powerplatform.ko.xlf +++ b/loc/translations-import/vscode-powerplatform.ko.xlf @@ -106,6 +106,10 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca 버전: {2} 유형: {3} + + Do not show again + 다시 표시 안 함 + Edit the site 사이트 편집 @@ -165,6 +169,14 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca {0} represents the version number Power Pages 생성기(v{0}) 설치 중... + + Learn more about Copilot + Copilot에 대해 자세히 알아보기 + + + Let Copilot help you code + 코딩에 Copilot 도움받기 + Managed 관리형 @@ -310,8 +322,8 @@ URL: {1} Select Type 유형 선택 - - Selection is empty! + + Selection is empty. 선택이 비어 있습니다. @@ -338,6 +350,10 @@ URL: {1} There’s a problem on the back end 백 엔드에 문제가 있습니다 + + Try Copilot for Power Pages + Power Pages에 Copilot 사용해 보기 + Try again 다시 시도 @@ -371,6 +387,10 @@ URL: {1} Webfile(s) added successfully 웹 파일 추가됨 + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + HTML, CSS, JS, Liquid 등 필요한 코드를 설명하면 AI가 대신 만들어줍니다. + You are editing a live, public site 라이브 상태인 공개 사이트를 편집하고 있습니다. diff --git a/loc/translations-import/vscode-powerplatform.pt-BR.xlf b/loc/translations-import/vscode-powerplatform.pt-BR.xlf index ac2a0885..870c76ed 100644 --- a/loc/translations-import/vscode-powerplatform.pt-BR.xlf +++ b/loc/translations-import/vscode-powerplatform.pt-BR.xlf @@ -106,6 +106,10 @@ Nome Exclusivo: {1} Versão: {2} Tipo: {3} + + Do not show again + Não mostrar novamente + Edit the site Editar o site @@ -165,6 +169,14 @@ Tipo: {3} {0} represents the version number Instalando o gerador do Power Pages(v{0})... + + Learn more about Copilot + Saiba mais sobre o Copilot + + + Let Copilot help you code + Permitir que o Copilot ajude você a codificar + Managed Gerenciado(a) @@ -310,9 +322,9 @@ ID da Organização: {3} Select Type Selecionar Tipo - - Selection is empty! - A seleção está vazia! + + Selection is empty. + A seleção está vazia. The Power Pages generator is ready for use in your VS Code extension! @@ -338,6 +350,10 @@ ID da Organização: {3} There’s a problem on the back end Há um problema no back-end + + Try Copilot for Power Pages + Testar o Copilot para Power Pages + Try again Tentar novamente @@ -371,6 +387,10 @@ ID da Organização: {3} Webfile(s) added successfully Arquivos da Web adicionados com êxito + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + Seja código HTML, CSS, JS ou Liquid, descreva apenas o que é necessário e deixe a IA criá-lo para você. + You are editing a live, public site Você está editando um site público ativo diff --git a/loc/translations-import/vscode-powerplatform.ru.xlf b/loc/translations-import/vscode-powerplatform.ru.xlf index c51e2da3..d226621e 100644 --- a/loc/translations-import/vscode-powerplatform.ru.xlf +++ b/loc/translations-import/vscode-powerplatform.ru.xlf @@ -106,6 +106,10 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca Версия: {2} Тип: {3} + + Do not show again + Больше не показывать + Edit the site Изменить сайт @@ -165,6 +169,14 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca {0} represents the version number Установка генератора Power Pages (v{0})... + + Learn more about Copilot + Подробнее о Copilot + + + Let Copilot help you code + Copilot поможет вам писать код + Managed Управляемые @@ -310,8 +322,8 @@ URL-адрес: {1} Select Type Выбрать тип - - Selection is empty! + + Selection is empty. Ничего не выбрано. @@ -338,6 +350,10 @@ URL-адрес: {1} There’s a problem on the back end Возникла проблема на сервере + + Try Copilot for Power Pages + Попробуйте Copilot для Power Pages + Try again Повторить попытку @@ -371,6 +387,10 @@ URL-адрес: {1} Webfile(s) added successfully Веб-файлы успешно добавлены + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + Неважно, создаете вы код HTML, CSS, JS или Liquid, просто опишите свою задачу и ИИ сгенерирует код за вас. + You are editing a live, public site Вы редактируете опубликованный открытый сайт diff --git a/loc/translations-import/vscode-powerplatform.tr.xlf b/loc/translations-import/vscode-powerplatform.tr.xlf index cf5172d4..5040c1bc 100644 --- a/loc/translations-import/vscode-powerplatform.tr.xlf +++ b/loc/translations-import/vscode-powerplatform.tr.xlf @@ -106,6 +106,10 @@ Benzersiz Ad: {1} Sürüm: {2} Tür: {3} + + Do not show again + Bir daha gösterme + Edit the site Siteyi düzenle @@ -165,6 +169,14 @@ Tür: {3} {0} represents the version number Power Pages oluşturucusu (v{0}) yükleniyor... + + Learn more about Copilot + Copilot hakkında daha fazla bilgi edinin + + + Let Copilot help you code + Copilot kodlamanıza yardımcı olsun + Managed Yönetilen @@ -310,9 +322,9 @@ Kuruluş Kimliği: {3} Select Type Tür Seç - - Selection is empty! - Seçim boş! + + Selection is empty. + Seçim boş. The Power Pages generator is ready for use in your VS Code extension! @@ -338,6 +350,10 @@ Kuruluş Kimliği: {3} There’s a problem on the back end Arka uçta bir sorun oluştu + + Try Copilot for Power Pages + Power Pages için Copilot'ı deneyin + Try again Yeniden dene @@ -371,6 +387,10 @@ Kuruluş Kimliği: {3} Webfile(s) added successfully Web dosyaları başarıyla eklendi + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + İster HTML, CSS, JS ister Liquid kodu olsun neye ihtiyacınız olduğunu açıklamanız yeterlidir; AI bunu sizin için oluşturur. + You are editing a live, public site Yayında olan, herkese açık bir siteyi düzenliyorsunuz diff --git a/loc/translations-import/vscode-powerplatform.zh-CN.xlf b/loc/translations-import/vscode-powerplatform.zh-CN.xlf index 9c0c2bf4..b12e879d 100644 --- a/loc/translations-import/vscode-powerplatform.zh-CN.xlf +++ b/loc/translations-import/vscode-powerplatform.zh-CN.xlf @@ -106,6 +106,10 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca 版本: {2} 类型: {3} + + Do not show again + 不再显示 + Edit the site 编辑站点 @@ -165,6 +169,14 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca {0} represents the version number 正在安装 Power Pages 生成器(v{0})... + + Learn more about Copilot + 详细了解 Copilot + + + Let Copilot help you code + 让 Copilot 帮助您编码 + Managed 托管 @@ -310,9 +322,9 @@ URL: {1} Select Type 选择类型 - - Selection is empty! - 选择是空的! + + Selection is empty. + 选择是空的。 The Power Pages generator is ready for use in your VS Code extension! @@ -338,6 +350,10 @@ URL: {1} There’s a problem on the back end 后端出现问题 + + Try Copilot for Power Pages + 尝试将 Copilot 用于 Power Pages + Try again 重试 @@ -371,6 +387,10 @@ URL: {1} Webfile(s) added successfully 已成功添加 Web 文件 + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + 无论是 HTML、CSS、JS 还是 Liquid 代码,只要描述您需要什么,让 AI 为您构建。 + You are editing a live, public site 您正在编辑一个公共活动站点 diff --git a/loc/translations-import/vscode-powerplatform.zh-TW.xlf b/loc/translations-import/vscode-powerplatform.zh-TW.xlf index c2303e04..c6c81d40 100644 --- a/loc/translations-import/vscode-powerplatform.zh-TW.xlf +++ b/loc/translations-import/vscode-powerplatform.zh-TW.xlf @@ -106,6 +106,10 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca 版本: {2} 類型: {3} + + Do not show again + 不要再顯示 + Edit the site 編輯網站 @@ -165,6 +169,14 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca {0} represents the version number 正在安裝 Power Pages 產生器 (v{0})... + + Learn more about Copilot + 深入了解 Copilot + + + Let Copilot help you code + 讓 Copilot 協助您撰寫程式碼 + Managed 受控 @@ -310,9 +322,9 @@ URL: {1} Select Type 選取類型 - - Selection is empty! - 選取項目是空的! + + Selection is empty. + 選取項目是空的。 The Power Pages generator is ready for use in your VS Code extension! @@ -338,6 +350,10 @@ URL: {1} There’s a problem on the back end 後端發生問題 + + Try Copilot for Power Pages + 試用 Power Pages 的 Copilot + Try again 再試一次 @@ -371,6 +387,10 @@ URL: {1} Webfile(s) added successfully 已成功新增 Web 檔案 + + Whether it’s HTML, CSS, JS, or Liquid code, just describe what you need and let AI build it for you. + 無論是 HTML、CSS、JS 或 Liquid 程式碼,只要描述您的需求,就可以交由 AI 為您建置。 + You are editing a live, public site 您正在編輯已上線、公開的網站 From 26fac4ef04958d5f01e8efb5eefa1c5bf89b884b Mon Sep 17 00:00:00 2001 From: tyaginidhi Date: Tue, 5 Dec 2023 08:27:35 +0530 Subject: [PATCH 6/8] CES survey telemetry endpoint update for copilots geo expansion (#785) * Update telemetry collection for non-US geos copilot feedback - this needs to be updated after follow-up with Privacy champs on EUDB boundaries * Add constants value --- src/common/ArtemisService.ts | 100 +-- src/common/copilot/PowerPagesCopilot.ts | 694 +++++++++--------- src/common/copilot/constants.ts | 29 +- src/common/copilot/model.ts | 25 +- src/common/copilot/user-feedback/CESSurvey.ts | 195 ++--- src/web/client/WebExtensionContext.ts | 2 +- src/web/client/extension.ts | 59 +- 7 files changed, 565 insertions(+), 539 deletions(-) diff --git a/src/common/ArtemisService.ts b/src/common/ArtemisService.ts index c8f930b5..a3501534 100644 --- a/src/common/ArtemisService.ts +++ b/src/common/ArtemisService.ts @@ -11,22 +11,22 @@ import { CopilotArtemisFailureEvent, CopilotArtemisSuccessEvent } from "./copilo export async function getIntelligenceEndpoint(orgId: string, telemetry: ITelemetry, sessionID: string) { - const artemisResponse = await fetchArtemisResponse(orgId, telemetry, sessionID); + const artemisResponse = await fetchArtemisResponse(orgId, telemetry, sessionID); - if (!artemisResponse) { - return null; - } + if (!artemisResponse) { + return { intelligenceEndpoint: null, geoName: null }; + } - const { geoName, environment, clusterNumber } = artemisResponse[0]; - sendTelemetryEvent(telemetry, { eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); + const { geoName, environment, clusterNumber } = artemisResponse[0]; + sendTelemetryEvent(telemetry, { eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); - if (geoName !== US_GEO) { - return COPILOT_UNAVAILABLE; - } + if (geoName !== US_GEO) { + return { intelligenceEndpoint: COPILOT_UNAVAILABLE, geoName: geoName }; + } - const intelligenceEndpoint = `https://aibuildertextapiservice.${geoName}-${'il' + clusterNumber}.gateway.${environment}.island.powerapps.com/v1.0/${orgId}/appintelligence/chat` + const intelligenceEndpoint = `https://aibuildertextapiservice.${geoName}-${'il' + clusterNumber}.gateway.${environment}.island.powerapps.com/v1.0/${orgId}/appintelligence/chat` - return intelligenceEndpoint; + return { intelligenceEndpoint: intelligenceEndpoint, geoName: geoName }; } @@ -38,35 +38,35 @@ export async function fetchArtemisResponse(orgId: string, telemetry: ITelemetry, const artemisResponse = await fetchIslandInfo(endpoints, telemetry, sessionID); return artemisResponse; - } +} async function fetchIslandInfo(endpoints: string[], telemetry: ITelemetry, sessionID: string) { - const requestInit: RequestInit = { - method: 'GET', - redirect: 'follow' - }; - - try { - const promises = endpoints.map(async endpoint => { - try { - const response = await fetch(endpoint, requestInit); - if (!response.ok) { - throw new Error('Request failed'); - } - return response.json(); - } catch (error) { + const requestInit: RequestInit = { + method: 'GET', + redirect: 'follow' + }; + + try { + const promises = endpoints.map(async endpoint => { + try { + const response = await fetch(endpoint, requestInit); + if (!response.ok) { + throw new Error('Request failed'); + } + return response.json(); + } catch (error) { + return null; + } + }); + + const responses = await Promise.all(promises); + const successfulResponses = responses.filter(response => response !== null); + return successfulResponses; + } catch (error) { + sendTelemetryEvent(telemetry, { eventName: CopilotArtemisFailureEvent, copilotSessionId: sessionID, error: error as Error }) return null; - } - }); - - const responses = await Promise.all(promises); - const successfulResponses = responses.filter(response => response !== null); - return successfulResponses; - } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotArtemisFailureEvent, copilotSessionId: sessionID, error: error as Error }) - return null; - } + } } @@ -79,18 +79,18 @@ async function fetchIslandInfo(endpoints: string[], telemetry: ITelemetry, sessi * Prod: https:// c7809087d9b84a00a78aa4b901caa2.3f.organization.api.powerplatform.com/artemis */ export function convertGuidToUrls(orgId: string) { - const updatedOrgId = orgId.replace(/-/g, ""); - const domain = updatedOrgId.slice(0, -1); - const domainProd = updatedOrgId.slice(0, -2); - const nonProdSegment = updatedOrgId.slice(-1); - const prodSegment = updatedOrgId.slice(-2); - const tstUrl = `https://${domain}.${nonProdSegment}.organization.api.test.powerplatform.com/gateway/cluster?api-version=1`; - const preprodUrl = `https://${domain}.${nonProdSegment}.organization.api.preprod.powerplatform.com/gateway/cluster?api-version=1`; - const prodUrl = `https://${domainProd}.${prodSegment}.organization.api.powerplatform.com/gateway/cluster?api-version=1`; - - return { - tstUrl, - preprodUrl, - prodUrl - }; + const updatedOrgId = orgId.replace(/-/g, ""); + const domain = updatedOrgId.slice(0, -1); + const domainProd = updatedOrgId.slice(0, -2); + const nonProdSegment = updatedOrgId.slice(-1); + const prodSegment = updatedOrgId.slice(-2); + const tstUrl = `https://${domain}.${nonProdSegment}.organization.api.test.powerplatform.com/gateway/cluster?api-version=1`; + const preprodUrl = `https://${domain}.${nonProdSegment}.organization.api.preprod.powerplatform.com/gateway/cluster?api-version=1`; + const prodUrl = `https://${domainProd}.${prodSegment}.organization.api.powerplatform.com/gateway/cluster?api-version=1`; + + return { + tstUrl, + preprodUrl, + prodUrl + }; } diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index 3a110d56..b06d3691 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -33,418 +33,424 @@ let sessionID: string; // Generated per session let orgID: string; let environmentName: string; let activeOrgUrl: string; +let tenantId: string | undefined; declare const IS_DESKTOP: string | undefined; //TODO: Check if it can be converted to singleton export class PowerPagesCopilot implements vscode.WebviewViewProvider { - public static readonly viewType = "powerpages.copilot"; - private _view?: vscode.WebviewView; - private readonly _pacWrapper?: PacWrapper; - private _extensionContext: vscode.ExtensionContext; - private readonly _disposables: vscode.Disposable[] = []; - private loginButtonRendered = false; - private telemetry: ITelemetry; - private aibEndpoint: string | null = null; - - constructor( - private readonly _extensionUri: vscode.Uri, - _context: vscode.ExtensionContext, - telemetry: ITelemetry | TelemetryReporter, - pacWrapper?: PacWrapper, - orgInfo?: IOrgInfo) { - this.telemetry = telemetry; - this._extensionContext = _context; - sessionID = uuidv4(); - this._pacWrapper = pacWrapper; - - this._disposables.push( - vscode.commands.registerCommand("powerpages.copilot.clearConversation", () => { - if (userName && orgID) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotClearChatEvent, copilotSessionId: sessionID, orgId: orgID }); - this.sendMessageToWebview({ type: "clearConversation" }); - sessionID = uuidv4(); - } - } - ) - ); + public static readonly viewType = "powerpages.copilot"; + private _view?: vscode.WebviewView; + private readonly _pacWrapper?: PacWrapper; + private _extensionContext: vscode.ExtensionContext; + private readonly _disposables: vscode.Disposable[] = []; + private loginButtonRendered = false; + private telemetry: ITelemetry; + private aibEndpoint: string | null = null; + private geoName: string | null = null; + + constructor( + private readonly _extensionUri: vscode.Uri, + _context: vscode.ExtensionContext, + telemetry: ITelemetry | TelemetryReporter, + pacWrapper?: PacWrapper, + orgInfo?: IOrgInfo) { + this.telemetry = telemetry; + this._extensionContext = _context; + sessionID = uuidv4(); + this._pacWrapper = pacWrapper; + this._disposables.push( + vscode.commands.registerCommand("powerpages.copilot.clearConversation", () => { + if (userName && orgID) { + sendTelemetryEvent(this.telemetry, { eventName: CopilotClearChatEvent, copilotSessionId: sessionID, orgId: orgID }); + this.sendMessageToWebview({ type: "clearConversation" }); + sessionID = uuidv4(); + } + } + ) + ); - if (SELECTED_CODE_INFO_ENABLED) { //TODO: Remove this check once the feature is ready - const handleSelectionChange = async (commandType: string) => { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return; - } - const selectedCode = getSelectedCode(editor); - const selectedCodeLineRange = getSelectedCodeLineRange(editor); - if(commandType === EXPLAIN_CODE && selectedCode.length === 0) { - // Show a message if the selection is empty and don't send the message to webview - vscode.window.showInformationMessage(vscode.l10n.t('Selection is empty.')); - return; - } - const withinTokenLimit = isWithinTokenLimit(selectedCode, 1000); - if(commandType === EXPLAIN_CODE) { - const tokenSize = encode(selectedCode).length; - sendTelemetryEvent(this.telemetry, { eventName: CopilotExplainCodeSize, copilotSessionId: sessionID, orgId: orgID, codeLineCount: String(selectedCodeLineRange.end - selectedCodeLineRange.start), tokenSize: String(tokenSize) }); - if(withinTokenLimit === false) { + if (SELECTED_CODE_INFO_ENABLED) { //TODO: Remove this check once the feature is ready + + const handleSelectionChange = async (commandType: string) => { + const editor = vscode.window.activeTextEditor; + if (!editor) { return; } - } - this.sendMessageToWebview({ type: commandType, value: { start: selectedCodeLineRange.start, end: selectedCodeLineRange.end, selectedCode: selectedCode, tokenSize: withinTokenLimit } }); - }; + const selectedCode = getSelectedCode(editor); + const selectedCodeLineRange = getSelectedCodeLineRange(editor); + if (commandType === EXPLAIN_CODE && selectedCode.length === 0) { + // Show a message if the selection is empty and don't send the message to webview + vscode.window.showInformationMessage(vscode.l10n.t('Selection is empty.')); + return; + } + const withinTokenLimit = isWithinTokenLimit(selectedCode, 1000); + if (commandType === EXPLAIN_CODE) { + const tokenSize = encode(selectedCode).length; + sendTelemetryEvent(this.telemetry, { eventName: CopilotExplainCodeSize, copilotSessionId: sessionID, orgId: orgID, codeLineCount: String(selectedCodeLineRange.end - selectedCodeLineRange.start), tokenSize: String(tokenSize) }); + if (withinTokenLimit === false) { + return; + } + } + this.sendMessageToWebview({ type: commandType, value: { start: selectedCodeLineRange.start, end: selectedCodeLineRange.end, selectedCode: selectedCode, tokenSize: withinTokenLimit } }); + }; - this._disposables.push( - vscode.window.onDidChangeTextEditorSelection(() => handleSelectionChange(SELECTED_CODE_INFO)), vscode.window.onDidChangeActiveTextEditor(() => handleSelectionChange(SELECTED_CODE_INFO)) - ); + this._disposables.push( + vscode.window.onDidChangeTextEditorSelection(() => handleSelectionChange(SELECTED_CODE_INFO)), vscode.window.onDidChangeActiveTextEditor(() => handleSelectionChange(SELECTED_CODE_INFO)) + ); - this._disposables.push( - vscode.commands.registerCommand("powerpages.copilot.explain", () => {sendTelemetryEvent(this.telemetry, { eventName: CopilotExplainCode, copilotSessionId: sessionID, orgId: orgID }); this.show(); handleSelectionChange(EXPLAIN_CODE)}) - ); - } + this._disposables.push( + vscode.commands.registerCommand("powerpages.copilot.explain", () => { sendTelemetryEvent(this.telemetry, { eventName: CopilotExplainCode, copilotSessionId: sessionID, orgId: orgID }); this.show(); handleSelectionChange(EXPLAIN_CODE) }) + ); + } + + if (this._pacWrapper) { + this.setupFileWatcher(); + } - if (this._pacWrapper) { - this.setupFileWatcher(); + if (orgInfo) { + orgID = orgInfo.orgId; + environmentName = orgInfo.environmentName; + activeOrgUrl = orgInfo.activeOrgUrl; + tenantId = orgInfo.tenantId; + } } - if (orgInfo) { - orgID = orgInfo.orgId; - environmentName = orgInfo.environmentName; - activeOrgUrl = orgInfo.activeOrgUrl; + public dispose(): void { + this._disposables.forEach(d => d.dispose()); } - } - - public dispose(): void { - this._disposables.forEach(d => d.dispose()); - } - - private setupFileWatcher() { - const watchPath = GetAuthProfileWatchPattern(); - if (watchPath) { - const watcher = vscode.workspace.createFileSystemWatcher(watchPath); - this._disposables.push( - watcher, - watcher.onDidChange(() => this.handleOrgChange()), - watcher.onDidCreate(() => this.handleOrgChange()), - watcher.onDidDelete(() => this.handleOrgChange()) - ); + + private setupFileWatcher() { + const watchPath = GetAuthProfileWatchPattern(); + if (watchPath) { + const watcher = vscode.workspace.createFileSystemWatcher(watchPath); + this._disposables.push( + watcher, + watcher.onDidChange(() => this.handleOrgChange()), + watcher.onDidCreate(() => this.handleOrgChange()), + watcher.onDidDelete(() => this.handleOrgChange()) + ); + } } - } - private async handleOrgChange() { - orgID = ''; - const pacOutput = await this._pacWrapper?.activeOrg(); + private async handleOrgChange() { + orgID = ''; + const pacOutput = await this._pacWrapper?.activeOrg(); - if (pacOutput && pacOutput.Status === PAC_SUCCESS) { - this.handleOrgChangeSuccess(pacOutput.Results); - } else if (this._view?.visible) { + if (pacOutput && pacOutput.Status === PAC_SUCCESS) { + this.handleOrgChangeSuccess(pacOutput.Results); + } else if (this._view?.visible) { - if (pacOutput && pacOutput.Status === PAC_SUCCESS) { - this.handleOrgChangeSuccess(pacOutput.Results); - } else if (this._view?.visible) { + if (pacOutput && pacOutput.Status === PAC_SUCCESS) { + this.handleOrgChangeSuccess(pacOutput.Results); + } else if (this._view?.visible) { - const userOrgUrl = await showInputBoxAndGetOrgUrl(); - if (!userOrgUrl) { - return; - } - const pacAuthCreateOutput = await showProgressWithNotification(vscode.l10n.t(AUTH_CREATE_MESSAGE), async () => { return await this._pacWrapper?.authCreateNewAuthProfileForOrg(userOrgUrl) }); - if (pacAuthCreateOutput && pacAuthCreateOutput.Status !== PAC_SUCCESS) { - vscode.window.showErrorMessage(AUTH_CREATE_FAILED); // TODO: Provide Experience to create auth profile - return; + const userOrgUrl = await showInputBoxAndGetOrgUrl(); + if (!userOrgUrl) { + return; + } + const pacAuthCreateOutput = await showProgressWithNotification(vscode.l10n.t(AUTH_CREATE_MESSAGE), async () => { return await this._pacWrapper?.authCreateNewAuthProfileForOrg(userOrgUrl) }); + if (pacAuthCreateOutput && pacAuthCreateOutput.Status !== PAC_SUCCESS) { + vscode.window.showErrorMessage(AUTH_CREATE_FAILED); // TODO: Provide Experience to create auth profile + return; + } + } } - } - } - } - - public async resolveWebviewView( - webviewView: vscode.WebviewView, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - context: vscode.WebviewViewResolveContext, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _token: vscode.CancellationToken - ) { - this._view = webviewView; - - webviewView.title = "Copilot In Power Pages" + (IS_DESKTOP ? "" : " [PREVIEW]"); - webviewView.description = "PREVIEW"; - webviewView.webview.options = { - // Allow scripts in the webview - enableScripts: true, - - localResourceRoots: [this._extensionUri], - }; - - const pacOutput = await this._pacWrapper?.activeOrg(); - - if(SELECTED_CODE_INFO_ENABLED){ - vscode.commands.executeCommand('setContext', 'powerpages.copilot.isVisible', true); - } - - if (pacOutput && pacOutput.Status === PAC_SUCCESS) { - await this.handleOrgChangeSuccess(pacOutput.Results); - } else if (!IS_DESKTOP && orgID && activeOrgUrl) { - await this.handleOrgChangeSuccess({ OrgId: orgID, UserId: userID, OrgUrl: activeOrgUrl } as ActiveOrgOutput); } - webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); + public async resolveWebviewView( + webviewView: vscode.WebviewView, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + context: vscode.WebviewViewResolveContext, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _token: vscode.CancellationToken + ) { + this._view = webviewView; + + webviewView.title = "Copilot In Power Pages" + (IS_DESKTOP ? "" : " [PREVIEW]"); + webviewView.description = "PREVIEW"; + webviewView.webview.options = { + // Allow scripts in the webview + enableScripts: true, + + localResourceRoots: [this._extensionUri], + }; - webviewView.webview.onDidReceiveMessage(async (data) => { - switch (data.type) { - case "webViewLoaded": { - // Send the localized strings to the copilot webview - this.sendMessageToWebview({type: 'copilotStrings', value: COPILOT_STRINGS}) - if (this.aibEndpoint === COPILOT_UNAVAILABLE) { - this.sendMessageToWebview({ type: 'Unavailable' }); - return; - } + const pacOutput = await this._pacWrapper?.activeOrg(); - sendTelemetryEvent(this.telemetry, { eventName: CopilotLoadedEvent, copilotSessionId: sessionID, orgId: orgID }); - this.sendMessageToWebview({ type: 'env' }); //TODO Use IS_DESKTOP - await this.checkAuthentication(); - if (orgID && userName) { - this.sendMessageToWebview({ type: 'isLoggedIn', value: true }); - this.sendMessageToWebview({ type: 'userName', value: userName }); - } else { - this.sendMessageToWebview({ type: 'isLoggedIn', value: false }); - this.loginButtonRendered = true; - } - this.sendMessageToWebview({ type: "welcomeScreen" }); - break; - } - case "login": { - this.handleLogin(); - break; + if (SELECTED_CODE_INFO_ENABLED) { + vscode.commands.executeCommand('setContext', 'powerpages.copilot.isVisible', true); } - case "newUserPrompt": { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserPromptedEvent, copilotSessionId: sessionID, aibEndpoint: this.aibEndpoint ?? '', orgId: orgID, isSuggestedPrompt: String(data.value.isSuggestedPrompt) }); //TODO: Add active Editor info - orgID - ? (async () => { - const { activeFileParams } = this.getActiveEditorContent(); - await this.authenticateAndSendAPIRequest(data.value.userPrompt, activeFileParams, orgID, this.telemetry); - })() - : (() => { - this.sendMessageToWebview({ type: 'apiResponse', value: AuthProfileNotFound }); - this.handleOrgChange(); - this.sendMessageToWebview({ type: 'enableInput' }); - })(); - - break; + + if (pacOutput && pacOutput.Status === PAC_SUCCESS) { + await this.handleOrgChangeSuccess(pacOutput.Results); + } else if (!IS_DESKTOP && orgID && activeOrgUrl) { + await this.handleOrgChangeSuccess({ OrgId: orgID, UserId: userID, OrgUrl: activeOrgUrl } as ActiveOrgOutput); } - case "insertCode": { - const escapedSnippet = escapeDollarSign(data.value); + webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); + + webviewView.webview.onDidReceiveMessage(async (data) => { + switch (data.type) { + case "webViewLoaded": { + // Send the localized strings to the copilot webview + this.sendMessageToWebview({ type: 'copilotStrings', value: COPILOT_STRINGS }) + if (this.aibEndpoint === COPILOT_UNAVAILABLE) { + this.sendMessageToWebview({ type: 'Unavailable' }); + return; + } + + sendTelemetryEvent(this.telemetry, { eventName: CopilotLoadedEvent, copilotSessionId: sessionID, orgId: orgID }); + this.sendMessageToWebview({ type: 'env' }); //TODO Use IS_DESKTOP + await this.checkAuthentication(); + if (orgID && userName) { + this.sendMessageToWebview({ type: 'isLoggedIn', value: true }); + this.sendMessageToWebview({ type: 'userName', value: userName }); + } else { + this.sendMessageToWebview({ type: 'isLoggedIn', value: false }); + this.loginButtonRendered = true; + } + this.sendMessageToWebview({ type: "welcomeScreen" }); + break; + } + case "login": { + this.handleLogin(); + break; + } + case "newUserPrompt": { + sendTelemetryEvent(this.telemetry, { eventName: CopilotUserPromptedEvent, copilotSessionId: sessionID, aibEndpoint: this.aibEndpoint ?? '', orgId: orgID, isSuggestedPrompt: String(data.value.isSuggestedPrompt) }); //TODO: Add active Editor info + orgID + ? (async () => { + const { activeFileParams } = this.getActiveEditorContent(); + await this.authenticateAndSendAPIRequest(data.value.userPrompt, activeFileParams, orgID, this.telemetry); + })() + : (() => { + this.sendMessageToWebview({ type: 'apiResponse', value: AuthProfileNotFound }); + this.handleOrgChange(); + this.sendMessageToWebview({ type: 'enableInput' }); + })(); + + break; + } + case "insertCode": { - vscode.window.activeTextEditor?.insertSnippet( - new vscode.SnippetString(`${escapedSnippet}`) - ); - sendTelemetryEvent(this.telemetry, { eventName: CopilotInsertCodeToEditorEvent, copilotSessionId: sessionID, orgId: orgID }); - break; - } - case "copyCodeToClipboard": { + const escapedSnippet = escapeDollarSign(data.value); - vscode.env.clipboard.writeText(data.value); - vscode.window.showInformationMessage(vscode.l10n.t('Copied to clipboard!')) - sendTelemetryEvent(this.telemetry, { eventName: CopilotCopyCodeToClipboardEvent, copilotSessionId: sessionID, orgId: orgID }); - break; - } - case "clearChat": { + vscode.window.activeTextEditor?.insertSnippet( + new vscode.SnippetString(`${escapedSnippet}`) + ); + sendTelemetryEvent(this.telemetry, { eventName: CopilotInsertCodeToEditorEvent, copilotSessionId: sessionID, orgId: orgID }); + break; + } + case "copyCodeToClipboard": { - sessionID = uuidv4(); - break; - } - case "userFeedback": { + vscode.env.clipboard.writeText(data.value); + vscode.window.showInformationMessage(vscode.l10n.t('Copied to clipboard!')) + sendTelemetryEvent(this.telemetry, { eventName: CopilotCopyCodeToClipboardEvent, copilotSessionId: sessionID, orgId: orgID }); + break; + } + case "clearChat": { - if (data.value === "thumbsUp") { + sessionID = uuidv4(); + break; + } + case "userFeedback": { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsUpEvent, copilotSessionId: sessionID, orgId: orgID }); - CESUserFeedback(this._extensionContext, sessionID, userID, "thumbsUp", this.telemetry) - } else if (data.value === "thumbsDown") { + if (data.value === "thumbsUp") { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsDownEvent, copilotSessionId: sessionID, orgId: orgID }); - CESUserFeedback(this._extensionContext, sessionID, userID, "thumbsDown", this.telemetry) - } - break; - } - case "walkthrough": { - sendTelemetryEvent(this.telemetry, { eventName: CopilotWalkthroughEvent, copilotSessionId: sessionID, orgId: orgID }); - openWalkthrough(this._extensionUri); - break; - } - case "codeLineCount": { - sendTelemetryEvent(this.telemetry, { eventName: CopilotCodeLineCountEvent, copilotSessionId: sessionID, codeLineCount: String(data.value), orgId: orgID }); - break; + sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsUpEvent, copilotSessionId: sessionID, orgId: orgID }); + CESUserFeedback(this._extensionContext, sessionID, userID, "thumbsUp", this.telemetry, this.geoName as string, tenantId) + } else if (data.value === "thumbsDown") { + + sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsDownEvent, copilotSessionId: sessionID, orgId: orgID }); + CESUserFeedback(this._extensionContext, sessionID, userID, "thumbsDown", this.telemetry, this.geoName as string, tenantId) + } + break; + } + case "walkthrough": { + sendTelemetryEvent(this.telemetry, { eventName: CopilotWalkthroughEvent, copilotSessionId: sessionID, orgId: orgID }); + openWalkthrough(this._extensionUri); + break; + } + case "codeLineCount": { + sendTelemetryEvent(this.telemetry, { eventName: CopilotCodeLineCountEvent, copilotSessionId: sessionID, codeLineCount: String(data.value), orgId: orgID }); + break; + } + } + }); + } + + public show() { + if (this._view) { + // Show the webview view + this._view.show(true); } - } - }); - } - - public show() { - if (this._view) { - // Show the webview view - this._view.show(true); } - } - private async handleLogin() { + private async handleLogin() { - const pacOutput = await this._pacWrapper?.activeOrg(); - if (pacOutput && pacOutput.Status === PAC_SUCCESS) { - this.handleOrgChangeSuccess.call(this, pacOutput.Results); + const pacOutput = await this._pacWrapper?.activeOrg(); + if (pacOutput && pacOutput.Status === PAC_SUCCESS) { + this.handleOrgChangeSuccess.call(this, pacOutput.Results); - intelligenceAPIAuthentication(this.telemetry, sessionID, orgID).then(({ accessToken, user, userId }) => { - this.intelligenceAPIAuthenticationHandler.call(this, accessToken, user, userId); - }); + intelligenceAPIAuthentication(this.telemetry, sessionID, orgID).then(({ accessToken, user, userId }) => { + this.intelligenceAPIAuthenticationHandler.call(this, accessToken, user, userId); + }); - } else if (this._view?.visible) { + } else if (this._view?.visible) { - const userOrgUrl = await showInputBoxAndGetOrgUrl(); - if (!userOrgUrl) { - this.sendMessageToWebview({ type: 'isLoggedIn', value: false }); + const userOrgUrl = await showInputBoxAndGetOrgUrl(); + if (!userOrgUrl) { + this.sendMessageToWebview({ type: 'isLoggedIn', value: false }); - if (!this.loginButtonRendered) { - this.sendMessageToWebview({ type: "welcomeScreen" }); - this.loginButtonRendered = true; // Set the flag to indicate that the login button has been rendered - } + if (!this.loginButtonRendered) { + this.sendMessageToWebview({ type: "welcomeScreen" }); + this.loginButtonRendered = true; // Set the flag to indicate that the login button has been rendered + } - return; - } - const pacAuthCreateOutput = await showProgressWithNotification(AUTH_CREATE_MESSAGE, async () => { return await this._pacWrapper?.authCreateNewAuthProfileForOrg(userOrgUrl) }); - pacAuthCreateOutput && pacAuthCreateOutput.Status === PAC_SUCCESS - ? intelligenceAPIAuthentication(this.telemetry, sessionID, orgID).then(({ accessToken, user, userId }) => - this.intelligenceAPIAuthenticationHandler.call(this, accessToken, user, userId) - ) - : vscode.window.showErrorMessage(AUTH_CREATE_FAILED); + return; + } + const pacAuthCreateOutput = await showProgressWithNotification(AUTH_CREATE_MESSAGE, async () => { return await this._pacWrapper?.authCreateNewAuthProfileForOrg(userOrgUrl) }); + pacAuthCreateOutput && pacAuthCreateOutput.Status === PAC_SUCCESS + ? intelligenceAPIAuthentication(this.telemetry, sessionID, orgID).then(({ accessToken, user, userId }) => + this.intelligenceAPIAuthenticationHandler.call(this, accessToken, user, userId) + ) + : vscode.window.showErrorMessage(AUTH_CREATE_FAILED); + } } - } - - private async checkAuthentication() { - const session = await vscode.authentication.getSession(PROVIDER_ID, [`${INTELLIGENCE_SCOPE_DEFAULT}`], { silent: true }); - if (session) { - intelligenceApiToken = session.accessToken; - userName = getUserName(session.account.label); - userID = session?.account.id.split("/").pop() ?? - session?.account.id; - } else { - intelligenceApiToken = ""; - userName = ""; + + private async checkAuthentication() { + const session = await vscode.authentication.getSession(PROVIDER_ID, [`${INTELLIGENCE_SCOPE_DEFAULT}`], { silent: true }); + if (session) { + intelligenceApiToken = session.accessToken; + userName = getUserName(session.account.label); + userID = session?.account.id.split("/").pop() ?? + session?.account.id; + } else { + intelligenceApiToken = ""; + userName = ""; + } } - } - private async authenticateAndSendAPIRequest(data: UserPrompt[], activeFileParams: IActiveFileParams, orgID: string, telemetry: ITelemetry) { - return intelligenceAPIAuthentication(telemetry, sessionID, orgID) - .then(async ({ accessToken, user, userId }) => { - intelligenceApiToken = accessToken; - userName = getUserName(user); - userID = userId; + private async authenticateAndSendAPIRequest(data: UserPrompt[], activeFileParams: IActiveFileParams, orgID: string, telemetry: ITelemetry) { + return intelligenceAPIAuthentication(telemetry, sessionID, orgID) + .then(async ({ accessToken, user, userId }) => { + intelligenceApiToken = accessToken; + userName = getUserName(user); + userID = userId; - this.sendMessageToWebview({ type: 'userName', value: userName }); + this.sendMessageToWebview({ type: 'userName', value: userName }); - let entityName = ""; - let entityColumns: string[] = []; + let entityName = ""; + let entityColumns: string[] = []; - if (activeFileParams.dataverseEntity == "adx_entityform" || activeFileParams.dataverseEntity == 'adx_entitylist') { - entityName = await getEntityName(telemetry, sessionID, activeFileParams.dataverseEntity); + if (activeFileParams.dataverseEntity == "adx_entityform" || activeFileParams.dataverseEntity == 'adx_entitylist') { + entityName = await getEntityName(telemetry, sessionID, activeFileParams.dataverseEntity); - const dataverseToken = await dataverseAuthentication(activeOrgUrl, true); + const dataverseToken = await dataverseAuthentication(activeOrgUrl, true); - entityColumns = await getEntityColumns(entityName, activeOrgUrl, dataverseToken, telemetry, sessionID); - } - return sendApiRequest(data, activeFileParams, orgID, intelligenceApiToken, sessionID, entityName, entityColumns, telemetry, this.aibEndpoint); - }) - .then(apiResponse => { - this.sendMessageToWebview({ type: 'apiResponse', value: apiResponse }); - this.sendMessageToWebview({ type: 'enableInput' }); - }); - } - - - private async handleOrgChangeSuccess(activeOrg: ActiveOrgOutput) { - if (IS_DESKTOP && orgID === activeOrg.OrgId) { - return; + entityColumns = await getEntityColumns(entityName, activeOrgUrl, dataverseToken, telemetry, sessionID); + } + return sendApiRequest(data, activeFileParams, orgID, intelligenceApiToken, sessionID, entityName, entityColumns, telemetry, this.aibEndpoint); + }) + .then(apiResponse => { + this.sendMessageToWebview({ type: 'apiResponse', value: apiResponse }); + this.sendMessageToWebview({ type: 'enableInput' }); + }); } - orgID = activeOrg.OrgId; - environmentName = activeOrg.FriendlyName; - userID = activeOrg.UserId; - activeOrgUrl = activeOrg.OrgUrl; - sessionID = uuidv4(); // Generate a new session ID on org change - sendTelemetryEvent(this.telemetry, { eventName: CopilotOrgChangedEvent, copilotSessionId: sessionID, orgId: orgID }); + private async handleOrgChangeSuccess(activeOrg: ActiveOrgOutput) { + if (IS_DESKTOP && orgID === activeOrg.OrgId) { + return; + } - this.aibEndpoint = await getIntelligenceEndpoint(orgID, this.telemetry, sessionID); - if (this.aibEndpoint === COPILOT_UNAVAILABLE) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotNotAvailable, copilotSessionId: sessionID, orgId: orgID }); - this.sendMessageToWebview({ type: 'Unavailable' }); - } else { - this.sendMessageToWebview({ type: 'Available' }); - } + orgID = activeOrg.OrgId; + environmentName = activeOrg.FriendlyName; + userID = activeOrg.UserId; + activeOrgUrl = activeOrg.OrgUrl; - if (IS_DESKTOP && this._view?.visible) { - showConnectedOrgMessage(environmentName, activeOrgUrl); - } - } + sessionID = uuidv4(); // Generate a new session ID on org change + sendTelemetryEvent(this.telemetry, { eventName: CopilotOrgChangedEvent, copilotSessionId: sessionID, orgId: orgID }); - private async intelligenceAPIAuthenticationHandler(accessToken: string, user: string, userId: string) { - if (accessToken && user) { - intelligenceApiToken = accessToken; - userName = getUserName(user); - userID = userId; + const { intelligenceEndpoint, geoName } = await getIntelligenceEndpoint(orgID, this.telemetry, sessionID); + this.aibEndpoint = intelligenceEndpoint; + this.geoName = geoName; - this.sendMessageToWebview({ type: 'isLoggedIn', value: true }) - this.sendMessageToWebview({ type: 'userName', value: userName }); - this.sendMessageToWebview({ type: "welcomeScreen" }); + if (this.aibEndpoint === COPILOT_UNAVAILABLE) { + sendTelemetryEvent(this.telemetry, { eventName: CopilotNotAvailable, copilotSessionId: sessionID, orgId: orgID }); + this.sendMessageToWebview({ type: 'Unavailable' }); + } else { + this.sendMessageToWebview({ type: 'Available' }); + } + + if (IS_DESKTOP && this._view?.visible) { + showConnectedOrgMessage(environmentName, activeOrgUrl); + } } - } - - private getActiveEditorContent(): IActiveFileData { - const activeEditor = vscode.window.activeTextEditor; - const activeFileData: IActiveFileData = { - activeFileContent: '', - activeFileParams: { - dataverseEntity: '', - entityField: '', - fieldType: '' - } as IActiveFileParams - }; - if (activeEditor) { - const document = activeEditor.document; - const fileName = document.fileName; - const relativeFileName = vscode.workspace.asRelativePath(fileName); - - const activeFileParams: string[] = getLastThreePartsOfFileName(relativeFileName); - - activeFileData.activeFileContent = document.getText(); - activeFileData.activeFileParams.dataverseEntity = DataverseEntityNameMap.get(activeFileParams[0]) || ""; - activeFileData.activeFileParams.entityField = EntityFieldMap.get(activeFileParams[1]) || ""; - activeFileData.activeFileParams.fieldType = FieldTypeMap.get(activeFileParams[2]) || ""; + + private async intelligenceAPIAuthenticationHandler(accessToken: string, user: string, userId: string) { + if (accessToken && user) { + intelligenceApiToken = accessToken; + userName = getUserName(user); + userID = userId; + + this.sendMessageToWebview({ type: 'isLoggedIn', value: true }) + this.sendMessageToWebview({ type: 'userName', value: userName }); + this.sendMessageToWebview({ type: "welcomeScreen" }); + } } - return activeFileData; - } + private getActiveEditorContent(): IActiveFileData { + const activeEditor = vscode.window.activeTextEditor; + const activeFileData: IActiveFileData = { + activeFileContent: '', + activeFileParams: { + dataverseEntity: '', + entityField: '', + fieldType: '' + } as IActiveFileParams + }; + if (activeEditor) { + const document = activeEditor.document; + const fileName = document.fileName; + const relativeFileName = vscode.workspace.asRelativePath(fileName); + + const activeFileParams: string[] = getLastThreePartsOfFileName(relativeFileName); + + activeFileData.activeFileContent = document.getText(); + activeFileData.activeFileParams.dataverseEntity = DataverseEntityNameMap.get(activeFileParams[0]) || ""; + activeFileData.activeFileParams.entityField = EntityFieldMap.get(activeFileParams[1]) || ""; + activeFileData.activeFileParams.fieldType = FieldTypeMap.get(activeFileParams[2]) || ""; + } - public sendMessageToWebview(message: WebViewMessage) { - if (this._view) { - this._view.webview.postMessage(message); + return activeFileData; } - } - private _getHtmlForWebview(webview: vscode.Webview) { - const copilotScriptPath = vscode.Uri.joinPath(this._extensionUri, 'src', 'common', 'copilot', 'assets', 'scripts', 'copilot.js'); - const copilotScriptUri = webview.asWebviewUri(copilotScriptPath); + public sendMessageToWebview(message: WebViewMessage) { + if (this._view) { + this._view.webview.postMessage(message); + } + } - const copilotStylePath = vscode.Uri.joinPath( - this._extensionUri, - ...CopilotStylePathSegments - ); - const copilotStyleUri = webview.asWebviewUri(copilotStylePath); + private _getHtmlForWebview(webview: vscode.Webview) { + const copilotScriptPath = vscode.Uri.joinPath(this._extensionUri, 'src', 'common', 'copilot', 'assets', 'scripts', 'copilot.js'); + const copilotScriptUri = webview.asWebviewUri(copilotScriptPath); - // Use a nonce to only allow specific scripts to be run - const nonce = getNonce(); + const copilotStylePath = vscode.Uri.joinPath( + this._extensionUri, + ...CopilotStylePathSegments + ); + const copilotStyleUri = webview.asWebviewUri(copilotStylePath); - //TODO: Add CSP - return ` + // Use a nonce to only allow specific scripts to be run + const nonce = getNonce(); + + //TODO: Add CSP + return ` @@ -482,5 +488,5 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { `; - } + } } diff --git a/src/common/copilot/constants.ts b/src/common/copilot/constants.ts index 34f0f4ba..954e83d4 100644 --- a/src/common/copilot/constants.ts +++ b/src/common/copilot/constants.ts @@ -16,6 +16,9 @@ export const sendIconSvg = ` ([ - ['webpage', 'adx_webpage'], - ['list', 'adx_entitylist'], - ['webtemplate', 'adx_webtemplate'], - ['basicform', 'adx_entityform'], - ['advancedformstep', 'adx_entityform'], + ['webpage', 'adx_webpage'], + ['list', 'adx_entitylist'], + ['webtemplate', 'adx_webtemplate'], + ['basicform', 'adx_entityform'], + ['advancedformstep', 'adx_entityform'], ]); export const EntityFieldMap = new Map([ - ['custom_javascript', 'adx_customjavascript'], - ['source', 'adx_source'], - ['copy', 'adx_copy'] + ['custom_javascript', 'adx_customjavascript'], + ['source', 'adx_source'], + ['copy', 'adx_copy'] ]); export const FieldTypeMap = new Map([ - ['js', 'JavaScript'], - ['html', 'html'] + ['js', 'JavaScript'], + ['html', 'html'] ]); export const AuthProfileNotFound = [{ displayText: "Active auth profile is not found or has expired. Create an Auth profile to start chatting with Copilot again.", code: '' }]; diff --git a/src/common/copilot/model.ts b/src/common/copilot/model.ts index e118f084..f2c49c61 100644 --- a/src/common/copilot/model.ts +++ b/src/common/copilot/model.ts @@ -4,24 +4,27 @@ */ export interface IFeedbackData { - IsDismissed: boolean; - ProductContext: { key: string, value: string }[]; - Feedbacks: { key: string, value: string }[]; + TenantId: string; + Geo: string; + IsDismissed: boolean; + ProductContext: { key: string, value: string }[]; + Feedbacks: { key: string, value: string }[]; } export interface IActiveFileParams { - dataverseEntity: string; - entityField: string; - fieldType: string; + dataverseEntity: string; + entityField: string; + fieldType: string; } export interface IActiveFileData { - activeFileParams: IActiveFileParams; - activeFileContent: string + activeFileParams: IActiveFileParams; + activeFileContent: string } export interface IOrgInfo { - orgId: string; - environmentName: string; - activeOrgUrl: string; + orgId: string; + environmentName: string; + activeOrgUrl: string; + tenantId?: string; } diff --git a/src/common/copilot/user-feedback/CESSurvey.ts b/src/common/copilot/user-feedback/CESSurvey.ts index b304425a..929d36ca 100644 --- a/src/common/copilot/user-feedback/CESSurvey.ts +++ b/src/common/copilot/user-feedback/CESSurvey.ts @@ -9,129 +9,137 @@ import { SurveyConstants } from "../../../web/client/common/constants"; import fetch from "node-fetch"; import { getNonce } from "../../Utils"; import { ITelemetry } from "../../../client/telemetry/ITelemetry"; -import { CopilotUserFeedbackFailureEvent, CopilotUserFeedbackSuccessEvent} from "../telemetry/telemetryConstants"; +import { CopilotUserFeedbackFailureEvent, CopilotUserFeedbackSuccessEvent } from "../telemetry/telemetryConstants"; import { sendTelemetryEvent } from "../telemetry/copilotTelemetry"; import { IFeedbackData } from "../model"; +import { EUROPE_GEO, UK_GEO } from "../constants"; let feedbackPanel: vscode.WebviewPanel | undefined; -export async function CESUserFeedback(context: vscode.ExtensionContext, sessionId: string, userID: string, thumbType: string, telemetry: ITelemetry, ) { +export async function CESUserFeedback(context: vscode.ExtensionContext, sessionId: string, userID: string, thumbType: string, telemetry: ITelemetry, geoName: string, tenantId?: string) { - if(feedbackPanel) { - feedbackPanel.dispose(); - } - - feedbackPanel = createFeedbackPanel(context); + if (feedbackPanel) { + feedbackPanel.dispose(); + } + + feedbackPanel = createFeedbackPanel(context); - feedbackPanel.webview.postMessage({ type: "thumbType", value: thumbType }); + feedbackPanel.webview.postMessage({ type: "thumbType", value: thumbType }); - const { feedbackCssUri, feedbackJsUri } = getWebviewURIs(context, feedbackPanel); + const { feedbackCssUri, feedbackJsUri } = getWebviewURIs(context, feedbackPanel); - const nonce = getNonce(); - const webview = feedbackPanel.webview - feedbackPanel.webview.html = getWebviewContent(feedbackCssUri, feedbackJsUri, nonce, webview); + const nonce = getNonce(); + const webview = feedbackPanel.webview + feedbackPanel.webview.html = getWebviewContent(feedbackCssUri, feedbackJsUri, nonce, webview); - const feedbackData = initializeFeedbackData(sessionId); + const feedbackData = initializeFeedbackData(sessionId, vscode.env.uiKind === vscode.UIKind.Web, geoName, tenantId); - const apiToken: string = await npsAuthentication(SurveyConstants.AUTHORIZATION_ENDPOINT); + const apiToken: string = await npsAuthentication(SurveyConstants.AUTHORIZATION_ENDPOINT); - const endpointUrl = `https://world.ces.microsoftcloud.com/api/v1/portalsdesigner/Surveys/powerpageschatgpt/Feedbacks?userId=${userID}`; + const endpointUrl = useEUEndpoint(geoName) ? `https://europe.ces.microsoftcloud.com/api/v1/portalsdesigner/Surveys/powerpageschatgpt/Feedbacks?userId=${userID}` : + `https://world.ces.microsoftcloud.com/api/v1/portalsdesigner/Surveys/powerpageschatgpt/Feedbacks?userId=${userID}`; - feedbackPanel.webview.onDidReceiveMessage( - async message => { - switch (message.command) { - case 'feedback': - await handleFeedbackSubmission(message.text, endpointUrl, apiToken, feedbackData, telemetry, thumbType, sessionId); - feedbackPanel?.dispose(); - break; - } - }, - undefined, - context.subscriptions - ); + feedbackPanel.webview.onDidReceiveMessage( + async message => { + switch (message.command) { + case 'feedback': + await handleFeedbackSubmission(message.text, endpointUrl, apiToken, feedbackData, telemetry, thumbType, sessionId); + feedbackPanel?.dispose(); + break; + } + }, + undefined, + context.subscriptions + ); } function createFeedbackPanel(context: vscode.ExtensionContext): vscode.WebviewPanel { - const feedbackPanel = vscode.window.createWebviewPanel( - "CESUserFeedback", - "Feedback", - vscode.ViewColumn.Seven, - { - enableScripts: true, - } - ); - - context.subscriptions.push(feedbackPanel); - - return feedbackPanel; + const feedbackPanel = vscode.window.createWebviewPanel( + "CESUserFeedback", + "Feedback", + vscode.ViewColumn.Seven, + { + enableScripts: true, + } + ); + + context.subscriptions.push(feedbackPanel); + + return feedbackPanel; } function getWebviewURIs(context: vscode.ExtensionContext, feedbackPanel: vscode.WebviewPanel): { feedbackCssUri: vscode.Uri, feedbackJsUri: vscode.Uri } { - const feedbackCssPath = vscode.Uri.joinPath(context.extensionUri, 'src', 'common', 'copilot', "user-feedback", "feedback.css"); - const feedbackCssUri = feedbackPanel.webview.asWebviewUri(feedbackCssPath); + const feedbackCssPath = vscode.Uri.joinPath(context.extensionUri, 'src', 'common', 'copilot', "user-feedback", "feedback.css"); + const feedbackCssUri = feedbackPanel.webview.asWebviewUri(feedbackCssPath); - const feedbackJsPath = vscode.Uri.joinPath(context.extensionUri, 'src', 'common', 'copilot', "user-feedback", "feedback.js"); - const feedbackJsUri = feedbackPanel.webview.asWebviewUri(feedbackJsPath); + const feedbackJsPath = vscode.Uri.joinPath(context.extensionUri, 'src', 'common', 'copilot', "user-feedback", "feedback.js"); + const feedbackJsUri = feedbackPanel.webview.asWebviewUri(feedbackJsPath); - return { feedbackCssUri, feedbackJsUri }; + return { feedbackCssUri, feedbackJsUri }; } -function initializeFeedbackData(sessionId: string): IFeedbackData { - const feedbackData: IFeedbackData = { - IsDismissed: false, - ProductContext: [ - { - key: 'sessionId', - value: sessionId - }, - { - key: 'scenario', - value: 'ProDevCopilot' - } - ], - Feedbacks: [ - { - key: 'comment', - value: '' - } - ] - }; - - return feedbackData; +function initializeFeedbackData(sessionId: string, isWebExtension: boolean, geoName: string, tenantId?: string): IFeedbackData { + const feedbackData: IFeedbackData = { + TenantId: tenantId ? tenantId : '', + Geo: geoName, + IsDismissed: false, + ProductContext: [ + { + key: 'sessionId', + value: sessionId + }, + { + key: 'scenario', + value: 'ProDevCopilot' + }, + { + key: 'subScenario', + value: isWebExtension ? 'Web' : 'Desktop' + } + ], + Feedbacks: [ + { + key: 'comment', + value: '' + } + ] + }; + + return feedbackData; } async function handleFeedbackSubmission(text: string, endpointUrl: string, apiToken: string, feedbackData: IFeedbackData, telemetry: ITelemetry, thumbType: string, sessionID: string) { - feedbackData.Feedbacks[0].value = thumbType + " " + text; - try { - const response = await fetch(endpointUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + apiToken, - }, - body: JSON.stringify(feedbackData) - }); - - if (response.ok) { - // Feedback sent successfully - const responseJson = await response.json(); - const feedbackId = responseJson.FeedbackId; - sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackSuccessEvent, feedbackType:thumbType, FeedbackId: feedbackId, copilotSessionId: sessionID }); - } else { - // Error sending feedback - const feedBackError = new Error(response.statusText); - sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackFailureEvent, feedbackType:thumbType, copilotSessionId: sessionID, error: feedBackError }); + feedbackData.Feedbacks[0].value = thumbType + " - " + text; + try { + const response = await fetch(endpointUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + apiToken, + }, + body: JSON.stringify(feedbackData) + }); + + if (response.ok) { + // Feedback sent successfully + const responseJson = await response.json(); + const feedbackId = responseJson.FeedbackId; + sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackSuccessEvent, feedbackType: thumbType, FeedbackId: feedbackId, copilotSessionId: sessionID }); + } else { + // Error sending feedback + const feedBackError = new Error(response.statusText); + sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackFailureEvent, feedbackType: thumbType, copilotSessionId: sessionID, error: feedBackError }); + } + } catch (error) { + // Network error or other exception + sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackFailureEvent, feedbackType: thumbType, copilotSessionId: sessionID, error: error as Error }); } - } catch (error) { - // Network error or other exception - sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackFailureEvent, feedbackType:thumbType, copilotSessionId: sessionID, error: error as Error }); - } } function getWebviewContent(feedbackCssUri: vscode.Uri, feedbackJsUri: vscode.Uri, nonce: string, webview: vscode.Webview) { - return ` + return ` @@ -154,3 +162,8 @@ function getWebviewContent(feedbackCssUri: vscode.Uri, feedbackJsUri: vscode.Uri `; } + +function useEUEndpoint(geoName: string): boolean { + return geoName === EUROPE_GEO || geoName === UK_GEO; +} + diff --git a/src/web/client/WebExtensionContext.ts b/src/web/client/WebExtensionContext.ts index 3cd6f2f5..658bcb86 100644 --- a/src/web/client/WebExtensionContext.ts +++ b/src/web/client/WebExtensionContext.ts @@ -687,7 +687,7 @@ class WebExtensionContext implements IWebExtensionContext { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - public async getWorkerScript(workerUrl : URL) : Promise { + public async getWorkerScript(workerUrl: URL): Promise { try { this.telemetry.sendInfoTelemetry( telemetryEventNames.WEB_EXTENSION_FETCH_WORKER_SCRIPT diff --git a/src/web/client/extension.ts b/src/web/client/extension.ts index 4832566b..48525204 100644 --- a/src/web/client/extension.ts +++ b/src/web/client/extension.ts @@ -320,40 +320,40 @@ export function createWebWorkerInstance( const workerUrl = new URL(webworkerMain.toString()); WebExtensionContext.getWorkerScript(workerUrl) - .then((workerScript) => { - const workerBlob = new Blob([workerScript], { - type: "application/javascript", - }); + .then((workerScript) => { + const workerBlob = new Blob([workerScript], { + type: "application/javascript", + }); - const urlObj = URL.createObjectURL(workerBlob); + const urlObj = URL.createObjectURL(workerBlob); - WebExtensionContext.setWorker(new Worker(urlObj)); + WebExtensionContext.setWorker(new Worker(urlObj)); - if (WebExtensionContext.worker !== undefined) { - WebExtensionContext.worker.onmessage = (event) => { - const { data } = event; + if (WebExtensionContext.worker !== undefined) { + WebExtensionContext.worker.onmessage = (event) => { + const { data } = event; - WebExtensionContext.containerId = event.data.containerId; + WebExtensionContext.containerId = event.data.containerId; - if (data.type === Constants.workerEventMessages.REMOVE_CONNECTED_USER) { - WebExtensionContext.removeConnectedUserInContext( - data.userId - ); - } - if (data.type === Constants.workerEventMessages.UPDATE_CONNECTED_USERS) { - WebExtensionContext.updateConnectedUsersInContext( - data.containerId, - data.userName, - data.userId, - data.entityId - ); - } - }; - } - }) + if (data.type === Constants.workerEventMessages.REMOVE_CONNECTED_USER) { + WebExtensionContext.removeConnectedUserInContext( + data.userId + ); + } + if (data.type === Constants.workerEventMessages.UPDATE_CONNECTED_USERS) { + WebExtensionContext.updateConnectedUsersInContext( + data.containerId, + data.userName, + data.userId, + data.entityId + ); + } + }; + } + }) WebExtensionContext.telemetry.sendInfoTelemetry(telemetryEventNames.WEB_EXTENSION_WEB_WORKER_REGISTERED); - } catch(error) { + } catch (error) { WebExtensionContext.telemetry.sendErrorTelemetry( telemetryEventNames.WEB_EXTENSION_WEB_WORKER_REGISTRATION_FAILED, createWebWorkerInstance.name, @@ -492,7 +492,8 @@ export function registerCopilot(context: vscode.ExtensionContext) { queryParameters.ORG_ID ) as string, environmentName: "", - activeOrgUrl: WebExtensionContext.urlParametersMap.get(queryParameters.ORG_URL) as string + activeOrgUrl: WebExtensionContext.urlParametersMap.get(queryParameters.ORG_URL) as string, + tenantId: WebExtensionContext.urlParametersMap.get(queryParameters.TENANT_ID) as string, } as IOrgInfo; const copilotPanel = new copilot.PowerPagesCopilot(context.extensionUri, @@ -555,7 +556,7 @@ async function logArtemisTelemetry() { queryParameters.ORG_ID ) as string - const artemisResponse = await fetchArtemisResponse(orgId, WebExtensionContext.telemetry.getTelemetryReporter()); + const artemisResponse = await fetchArtemisResponse(orgId, WebExtensionContext.telemetry.getTelemetryReporter()); if (!artemisResponse) { return; From 323293b9a101adf3d61f027648541402d5bdf4c5 Mon Sep 17 00:00:00 2001 From: Ritik Ramuka <56073559+ritikramuka@users.noreply.github.com> Date: Fri, 8 Dec 2023 08:25:54 +0530 Subject: [PATCH 7/8] setting rootWebPageId of your current location against your current connection in container (#789) --- src/web/client/common/interfaces.ts | 1 + src/web/client/common/worker/webworker.js | 8 +++++++- src/web/client/extension.ts | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/web/client/common/interfaces.ts b/src/web/client/common/interfaces.ts index eecd3993..036ae989 100644 --- a/src/web/client/common/interfaces.ts +++ b/src/web/client/common/interfaces.ts @@ -16,6 +16,7 @@ export interface IAttributePath { export interface IEntityInfo { entityId: string; entityName: string; + rootWebPageId?: string; } export interface IFileInfo { diff --git a/src/web/client/common/worker/webworker.js b/src/web/client/common/worker/webworker.js index 6461f74b..c107950a 100644 --- a/src/web/client/common/worker/webworker.js +++ b/src/web/client/common/worker/webworker.js @@ -87,7 +87,13 @@ async function loadContainer(config, swpId, entityInfo) { const existingMembers = audience.getMembers(); - // TODO: yet to be decided how entity id will be passed in container from vscode side + const myself = audience.getMyself(); + + if (audience && myself) { + const myConnectionId = audience['container'].clientId; + const entityIdObj = new Array(entityInfo.rootWebPageId); + (await container.initialObjects.sharedState.get('selection').get()).set(myConnectionId, entityIdObj); + } audience.on("memberRemoved", (clientId, member) => { if (!existingMembers.get(member.userId)) { diff --git a/src/web/client/extension.ts b/src/web/client/extension.ts index 48525204..ea69b25a 100644 --- a/src/web/client/extension.ts +++ b/src/web/client/extension.ts @@ -238,7 +238,8 @@ export function processWorkspaceStateChanges(context: vscode.ExtensionContext) { const document = tab.input; const entityInfo: IEntityInfo = { entityId: getFileEntityId(document.uri.fsPath), - entityName: getFileEntityName(document.uri.fsPath) + entityName: getFileEntityName(document.uri.fsPath), + rootWebPageId: WebExtensionContext.entityDataMap.getEntityMap.get(getFileEntityId(document.uri.fsPath))?.rootWebPageId as string }; if (entityInfo.entityId && entityInfo.entityName) { context.workspaceState.update(document.uri.fsPath, entityInfo); From 608dbe17923e050735a469c9b173287fab5b849b Mon Sep 17 00:00:00 2001 From: Ritik Ramuka <56073559+ritikramuka@users.noreply.github.com> Date: Fri, 8 Dec 2023 08:30:15 +0530 Subject: [PATCH 8/8] People Present On The Site Tree Webview (#788) * tree webview for users present on power pages studio and vscode for web * inline icon for mail and teams chat * open teams chat and mail * renamed provider * renamed provider * renamed provider * formatting reverted * formatting reverted * microsoft teams chat icon updated * Named command, method and provider more logically --- package.json | 30 +++++++ src/web/client/WebExtensionContext.ts | 8 +- .../dark/microsoftTeams.svg | 3 + .../light/microsoftTeams.svg | 3 + src/web/client/common/constants.ts | 4 + src/web/client/extension.ts | 19 +++++ src/web/client/utilities/commonUtil.ts | 8 ++ .../webViews/userCollaborationProvider.ts | 79 +++++++++++++++++++ 8 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/web/client/assets/microsoftTeamsIcon/dark/microsoftTeams.svg create mode 100644 src/web/client/assets/microsoftTeamsIcon/light/microsoftTeams.svg create mode 100644 src/web/client/webViews/userCollaborationProvider.ts diff --git a/package.json b/package.json index 1f8868e3..800844b4 100644 --- a/package.json +++ b/package.json @@ -154,6 +154,19 @@ } ], "commands": [ + { + "command": "powerpages.collaboration.openTeamsChat", + "title": "Open Teams Chat", + "icon": { + "light": "src/web/client/assets/microsoftTeamsIcon/light/microsoftTeams.svg", + "dark": "src/web/client/assets/microsoftTeamsIcon/dark/microsoftTeams.svg" + } + }, + { + "command": "powerpages.collaboration.openMail", + "title": "Open Mail", + "icon": "$(mail)" + }, { "command": "powerpages.powerPagesFileExplorer.powerPagesRuntimePreview", "title": "Preview site", @@ -862,6 +875,16 @@ { "command": "pacCLI.envAndSolutionsPanel.copyOrganizationId", "when": "!virtualWorkspace && view == pacCLI.envAndSolutionsPanel && viewItem == ENVIRONMENT" + }, + { + "command": "powerpages.collaboration.openTeamsChat", + "group": "inline", + "when": "viewItem == userNode" + }, + { + "command": "powerpages.collaboration.openMail", + "group": "inline", + "when": "viewItem == userNode" } ] }, @@ -913,6 +936,13 @@ "icon": "./src/web/client/assets/powerPages.svg", "contextualTitle": "%microsoft-powerplatform-portals.navigation-loop.powerPagesFileExplorer.title%", "visibility": "visible" + }, + { + "id": "powerpages.collaborationView", + "name": "People On The Site", + "when": "isWeb && virtualWorkspace", + "contextualTitle": "People On The Site", + "visibility": "visible" } ] }, diff --git a/src/web/client/WebExtensionContext.ts b/src/web/client/WebExtensionContext.ts index 658bcb86..26b3a14f 100644 --- a/src/web/client/WebExtensionContext.ts +++ b/src/web/client/WebExtensionContext.ts @@ -32,6 +32,7 @@ import { isMultifileEnabled } from "./utilities/commonUtil"; import { UserDataMap } from "./context/userDataMap"; import { EntityForeignKeyDataMap } from "./context/entityForeignKeyDataMap"; import { QuickPickProvider } from "./webViews/QuickPickProvider"; +import { UserCollaborationProvider } from "./webViews/userCollaborationProvider"; export interface IWebExtensionContext { // From portalSchema properties @@ -112,6 +113,7 @@ class WebExtensionContext implements IWebExtensionContext { private _containerId: string; private _connectedUsers: UserDataMap; private _quickPickProvider: QuickPickProvider; + private _userCollaborationProvider: UserCollaborationProvider; public get schemaDataSourcePropertiesMap() { return this._schemaDataSourcePropertiesMap; @@ -215,7 +217,10 @@ class WebExtensionContext implements IWebExtensionContext { public get quickPickProvider() { return this._quickPickProvider; } - + public get userCollaborationProvider() { + return this._userCollaborationProvider; + } + constructor() { this._schemaDataSourcePropertiesMap = new Map(); this._schemaEntitiesMap = new Map>(); @@ -249,6 +254,7 @@ class WebExtensionContext implements IWebExtensionContext { this._containerId = ""; this._connectedUsers = new UserDataMap(); this._quickPickProvider = new QuickPickProvider(); + this._userCollaborationProvider = new UserCollaborationProvider(); } public setWebExtensionContext( diff --git a/src/web/client/assets/microsoftTeamsIcon/dark/microsoftTeams.svg b/src/web/client/assets/microsoftTeamsIcon/dark/microsoftTeams.svg new file mode 100644 index 00000000..ea7d130a --- /dev/null +++ b/src/web/client/assets/microsoftTeamsIcon/dark/microsoftTeams.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/web/client/assets/microsoftTeamsIcon/light/microsoftTeams.svg b/src/web/client/assets/microsoftTeamsIcon/light/microsoftTeams.svg new file mode 100644 index 00000000..d34e8b6a --- /dev/null +++ b/src/web/client/assets/microsoftTeamsIcon/light/microsoftTeams.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/web/client/common/constants.ts b/src/web/client/common/constants.ts index 67c256d7..ecaaf6a3 100644 --- a/src/web/client/common/constants.ts +++ b/src/web/client/common/constants.ts @@ -132,3 +132,7 @@ export enum GraphService { } export const MICROSOFT_GRAPH_PROFILE_PICTURE_SERVICE_CALL = "/photo/$value"; + +// User collaboration constants +export const USER_COLLABORATION_CONTEXT_VALUE = "userNode"; +export const THEME_ICON_ACCOUNT = "account"; diff --git a/src/web/client/extension.ts b/src/web/client/extension.ts index ea69b25a..3bd26eea 100644 --- a/src/web/client/extension.ts +++ b/src/web/client/extension.ts @@ -195,6 +195,22 @@ export function activate(context: vscode.ExtensionContext): void { showWalkthrough(context, WebExtensionContext.telemetry); } +export function registerCollaborationView() { + vscode.window.registerTreeDataProvider('powerpages.collaborationView', WebExtensionContext.userCollaborationProvider); + vscode.commands.registerCommand( + "powerpages.collaboration.openTeamsChat", + (event) => { + WebExtensionContext.userCollaborationProvider.openTeamsChat(event.id) + } + ); + vscode.commands.registerCommand( + "powerpages.collaboration.openMail", + (event) => { + WebExtensionContext.userCollaborationProvider.openMail(event.id) + } + ); +} + export function powerPagesNavigation() { const powerPagesNavigationProvider = new PowerPagesNavigationProvider(); vscode.window.registerTreeDataProvider('powerpages.powerPagesFileExplorer', powerPagesNavigationProvider); @@ -302,6 +318,7 @@ export function processWillSaveDocument(context: vscode.ExtensionContext) { export function processWillStartCollaboartion(context: vscode.ExtensionContext) { // feature in progress, hence disabling it if (isCoPresenceEnabled()) { + registerCollaborationView(); vscode.commands.registerCommand('powerPlatform.previewCurrentActiveUsers', () => WebExtensionContext.quickPickProvider.showQuickPick()); createWebWorkerInstance(context); } @@ -340,6 +357,7 @@ export function createWebWorkerInstance( WebExtensionContext.removeConnectedUserInContext( data.userId ); + WebExtensionContext.userCollaborationProvider.refresh(); } if (data.type === Constants.workerEventMessages.UPDATE_CONNECTED_USERS) { WebExtensionContext.updateConnectedUsersInContext( @@ -348,6 +366,7 @@ export function createWebWorkerInstance( data.userId, data.entityId ); + WebExtensionContext.userCollaborationProvider.refresh(); } }; } diff --git a/src/web/client/utilities/commonUtil.ts b/src/web/client/utilities/commonUtil.ts index f2ce812c..f1828075 100644 --- a/src/web/client/utilities/commonUtil.ts +++ b/src/web/client/utilities/commonUtil.ts @@ -266,3 +266,11 @@ export function getImageFileContent(fileFsPath: string, fileContent: Uint8Array) return webFileV2 ? fileContent : convertContentToString(fileContent, true); } + +export function getTeamChatURL (mail: string) { + return new URL(mail, "https://teams.microsoft.com/l/chat/0/0?users="); +} + +export function getMailToPath (mail: string) { + return `mailto:${mail}`; +} diff --git a/src/web/client/webViews/userCollaborationProvider.ts b/src/web/client/webViews/userCollaborationProvider.ts new file mode 100644 index 00000000..f69df3ac --- /dev/null +++ b/src/web/client/webViews/userCollaborationProvider.ts @@ -0,0 +1,79 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import * as vscode from "vscode"; +import WebExtensionContext from "../WebExtensionContext"; +import { GraphClientService } from "../services/graphClientService"; +import { getMailToPath, getTeamChatURL } from "../utilities/commonUtil"; +import * as Constants from "../common/constants"; + +export class UserCollaborationProvider + implements vscode.TreeDataProvider +{ + private graphClientService: GraphClientService; + private _onDidChangeTreeData: vscode.EventEmitter< + UserNode | undefined | void + > = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = + this._onDidChangeTreeData.event; + + constructor() { + this.graphClientService = new GraphClientService(); + } + + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + getTreeItem(element: UserNode): vscode.TreeItem { + return element; + } + + getChildren(): Thenable { + return Promise.resolve(this.getConnectedUsers()); + } + + getConnectedUsers(): UserNode[] { + const connectedUsersMap = WebExtensionContext.connectedUsers.getUserMap; + const connectedUsers: UserNode[] = Array.from( + connectedUsersMap.entries() + ).map(([, value]) => { + return new UserNode( + value._userName, + value._userId, + vscode.TreeItemCollapsibleState.None + ); + }); + + return connectedUsers; + } + + async openTeamsChat(userId: string): Promise { + const mail = await this.graphClientService.getUserEmail(userId); + const teamsChatLink = getTeamChatURL(mail); + vscode.env.openExternal(vscode.Uri.parse(teamsChatLink.href)); + } + + async openMail(userId: string): Promise { + const mail = await this.graphClientService.getUserEmail(userId); + const mailToPath = getMailToPath(mail); + vscode.env.openExternal(vscode.Uri.parse(mailToPath)); + } +} + +export class UserNode extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly id: string, + public readonly collapsibleState: vscode.TreeItemCollapsibleState + ) { + super(label, collapsibleState); + + this.tooltip = this.label; + this.iconPath = new vscode.ThemeIcon(Constants.THEME_ICON_ACCOUNT); + } + + contextValue = Constants.USER_COLLABORATION_CONTEXT_VALUE; +}