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;