diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index f7a15077..eb0a9882 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -14,7 +14,7 @@ import { intelligenceAPIAuthentication } from '../../services/AuthenticationProv import { ActiveOrgOutput } from '../../../client/pac/PacTypes'; import { AUTHENTICATION_FAILED_MSG, COPILOT_NOT_AVAILABLE_MSG, COPILOT_NOT_RELEASED_MSG, DISCLAIMER_MESSAGE, INVALID_RESPONSE, NO_PROMPT_MESSAGE, PAC_AUTH_INPUT, PAC_AUTH_NOT_FOUND, POWERPAGES_CHAT_PARTICIPANT_ID, POWERPAGES_COMMANDS, RESPONSE_AWAITED_MSG, RESPONSE_SCENARIOS, SKIP_CODES, STATER_PROMPTS, WELCOME_MESSAGE, WELCOME_PROMPT } from './PowerPagesChatParticipantConstants'; import { ORG_DETAILS_KEY, handleOrgChangeSuccess, initializeOrgDetails } from '../../utilities/OrgHandlerUtils'; -import { createAndReferenceLocation, getComponentInfo, getEndpoint, provideChatParticipantFollowups, handleChatParticipantFeedback, createErrorResult, createSuccessResult, removeChatVariables } from './PowerPagesChatParticipantUtils'; +import { createAndReferenceLocation, getComponentInfo, getEndpoint, provideChatParticipantFollowups, handleChatParticipantFeedback, createErrorResult, createSuccessResult, removeChatVariables, registerButtonCommands } from './PowerPagesChatParticipantUtils'; import { checkCopilotAvailability, fetchRelatedFiles, getActiveEditorContent } from '../../utilities/Utils'; import { IIntelligenceAPIEndpointInformation } from '../../services/Interfaces'; import { v4 as uuidv4 } from 'uuid'; @@ -63,6 +63,8 @@ export class PowerPagesChatParticipant { this._pacWrapper = pacWrapper; + registerButtonCommands(); + this._disposables.push(orgChangeEvent(async (orgDetails: ActiveOrgOutput) => { await this.handleOrgChangeSuccess(orgDetails); })); @@ -131,9 +133,13 @@ export class PowerPagesChatParticipant { const userId = intelligenceApiAuthResponse.userId; const intelligenceAPIEndpointInfo = await getEndpoint(this.orgID, this.environmentID, this.telemetry, this.cachedEndpoint, this.powerPagesAgentSessionId); + if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { + return createErrorResult(COPILOT_NOT_AVAILABLE_MSG, RESPONSE_SCENARIOS.COPILOT_NOT_AVAILABLE, this.orgID); + } + const copilotAvailabilityStatus = checkCopilotAvailability(intelligenceAPIEndpointInfo.intelligenceEndpoint, this.orgID, this.telemetry, this.powerPagesAgentSessionId); - if (!copilotAvailabilityStatus || !intelligenceAPIEndpointInfo.intelligenceEndpoint) { + if (!copilotAvailabilityStatus) { return createErrorResult(COPILOT_NOT_AVAILABLE_MSG, RESPONSE_SCENARIOS.COPILOT_NOT_AVAILABLE, this.orgID); } diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts index 94749968..8fd130f7 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts @@ -11,6 +11,9 @@ import { ITelemetry } from "../../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { ArtemisService } from "../../services/ArtemisService"; import { dataverseAuthentication } from "../../services/AuthenticationProvider"; import { IIntelligenceAPIEndpointInformation } from "../../services/Interfaces"; +import { EditableFileSystemProvider } from "../../utilities/EditableFileSystemProvider"; +import { CREATE_SITE_BTN_CMD } from "./commands/create-site/CreateSiteConstants"; +import { collectSiteCreationInputs, getUpdatedPageContent } from "./commands/create-site/CreateSiteHelper"; import { SUPPORTED_ENTITIES, EXPLAIN_CODE_PROMPT, FORM_PROMPT, LIST_PROMPT, STATER_PROMPTS, WEB_API_PROMPT } from "./PowerPagesChatParticipantConstants"; import { VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_SCENARIO_FEEDBACK_THUMBSUP, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_SCENARIO_FEEDBACK_THUMBSDOWN } from "./PowerPagesChatParticipantTelemetryConstants"; import { IComponentInfo, IPowerPagesChatResult } from "./PowerPagesChatParticipantTypes"; @@ -127,3 +130,22 @@ export function removeChatVariables(userPrompt: string): string { return userPrompt.replace(regex, '').trim(); } + +export function registerButtonCommands() { + vscode.commands.registerCommand(CREATE_SITE_BTN_CMD, async (siteName: string, sitePages, envList, contentProvider: EditableFileSystemProvider, isCreateSiteInputsReceived) => { + if (!isCreateSiteInputsReceived) { + //Update Page Content will be used for the site creation + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sitePages.map((page: any) => { + return { + ...page, + code: getUpdatedPageContent(contentProvider, page.metadata.pageTitle) + } + }); + const siteCreateInputs = await collectSiteCreationInputs(siteName, envList); + if (siteCreateInputs) { + isCreateSiteInputsReceived = true; + } + } + }); +} diff --git a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteCommand.ts b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteCommand.ts index f6212e79..61bd117b 100644 --- a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteCommand.ts +++ b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteCommand.ts @@ -19,7 +19,7 @@ export class CreateSiteCommand implements Command { try { // eslint-disable-next-line @typescript-eslint/no-unused-vars const result = await createSite({ - intelligenceEndpoint: intelligenceAPIEndpointInfo.intelligenceEndpoint, + intelligenceAPIEndpointInfo, intelligenceApiToken, userPrompt: request.prompt, sessionId: powerPagesAgentSessionId, diff --git a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteConstants.ts b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteConstants.ts index eac8c582..3ebbfd0a 100644 --- a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteConstants.ts +++ b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteConstants.ts @@ -7,3 +7,10 @@ export const EDITABLE_SCHEME = 'editable'; export const ENGLISH = "English"; export const MIN_PAGES = 7; export const MAX_PAGES = 7; +export const SITE_CREATE_INPUTS = 'New Power Pages Site'; +export const ENVIRONMENT_FOR_SITE_CREATION = 'Select Environment for Site Creation'; +export const SITE_NAME = 'Enter Site Name'; +export const SITE_NAME_REQUIRED = 'Site Name is required'; +export const CREATE_SITE_BTN_CMD = 'create-site-inputs'; +export const CREATE_SITE_BTN_TITLE = 'Create Site'; +export const CREATE_SITE_BTN_TOOLTIP = 'Create a new Power Pages site'; diff --git a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts index 77d04bc6..b465174b 100644 --- a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts +++ b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts @@ -11,13 +11,15 @@ import { NL2SITE_REQUEST_FAILED, NL2PAGE_GENERATING_WEBPAGES, NL2PAGE_RESPONSE_F import { oneDSLoggerWrapper } from '../../../../OneDSLoggerTelemetry/oneDSLoggerWrapper'; import { VSCODE_EXTENSION_NL2PAGE_REQUEST, VSCODE_EXTENSION_NL2SITE_REQUEST, VSCODE_EXTENSION_PREVIEW_SITE_PAGES, VSCODE_EXTENSION_PREVIEW_SITE_PAGES_ERROR } from '../../PowerPagesChatParticipantTelemetryConstants'; import { EditableFileSystemProvider } from '../../../../utilities/EditableFileSystemProvider'; -import { HTML_FILE_EXTENSION, UTF8_ENCODING } from '../../../../constants'; -import { EDITABLE_SCHEME } from './CreateSiteConstants'; -import { ICreateSiteOptions, IPreviewSitePagesContentOptions } from './CreateSiteTypes'; +import { HTML_FILE_EXTENSION, IEnvInfo, UTF8_ENCODING } from '../../../../constants'; +import { CREATE_SITE_BTN_CMD, CREATE_SITE_BTN_TITLE, CREATE_SITE_BTN_TOOLTIP, EDITABLE_SCHEME, ENVIRONMENT_FOR_SITE_CREATION, SITE_CREATE_INPUTS, SITE_NAME, SITE_NAME_REQUIRED } from './CreateSiteConstants'; +import { ICreateSiteOptions, IPreviewSitePagesContentOptions, ISiteInputState } from './CreateSiteTypes'; +import { MultiStepInput } from '../../../../utilities/MultiStepInput'; +import { getEnvList } from '../../../../utilities/Utils'; export const createSite = async (createSiteOptions: ICreateSiteOptions) => { const { - intelligenceEndpoint, + intelligenceAPIEndpointInfo, intelligenceApiToken, userPrompt, sessionId, @@ -29,12 +31,22 @@ export const createSite = async (createSiteOptions: ICreateSiteOptions) => { extensionContext } = createSiteOptions; - const { siteName, siteDescription, sitePages } = await fetchSiteAndPageData(intelligenceEndpoint, intelligenceApiToken, userPrompt, sessionId, telemetry, stream, orgId, envId, userId); + if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { + return; + } + const { siteName, siteDescription, sitePages } = await fetchSiteAndPageData(intelligenceAPIEndpointInfo.intelligenceEndpoint, intelligenceApiToken, userPrompt, sessionId, telemetry, stream, orgId, envId, userId); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const contentProvider = previewSitePagesContent({sitePages, stream, extensionContext, telemetry, sessionId, orgId, envId, userId}); + const contentProvider = previewSitePagesContent({ sitePages, stream, extensionContext, telemetry, sessionId, orgId, envId, userId }); + + const envList = await getEnvList(telemetry, intelligenceAPIEndpointInfo.endpointStamp); - // TODO: Implement the create site button click handler + stream.button({ + command: CREATE_SITE_BTN_CMD, + title: CREATE_SITE_BTN_TITLE, + tooltip: CREATE_SITE_BTN_TOOLTIP, + arguments: [siteName, envList, contentProvider, false], + }); return { siteName, @@ -117,4 +129,63 @@ function previewSitePagesContent( throw error; } } +// Function to get updated content +export function getUpdatedPageContent(contentProvider: EditableFileSystemProvider, pageName: string): string { + const pageUri = vscode.Uri.parse(`${EDITABLE_SCHEME}:/${pageName}${HTML_FILE_EXTENSION}`); + return contentProvider.getFileContent(pageUri); +} + +export async function collectSiteCreationInputs(siteName: string, envList: IEnvInfo[]) { + const envNames: vscode.QuickPickItem[] = envList.map((env: IEnvInfo) => { + return { + label: env.envDisplayName, + description: env.orgUrl, + }; + }); + + const title = vscode.l10n.t(SITE_CREATE_INPUTS); + + async function collectInputs() { + const state = {} as Partial; + await MultiStepInput.run((input) => selectEnvName(input, state)); + return state as ISiteInputState; + } + async function selectEnvName( + input: MultiStepInput, + state: Partial + ) { + const pick = await input.showQuickPick({ + title, + step: 1, + totalSteps: 2, + placeholder: vscode.l10n.t(ENVIRONMENT_FOR_SITE_CREATION), + items: envNames, + activeItem: + typeof state.envName !== "string" + ? state.envName + : undefined, + }); + state.envName = pick.label; + state.OrgUrl = pick.description; + return (input: MultiStepInput) => inputSiteName(input, state); + } + + async function inputSiteName( + input: MultiStepInput, + state: Partial + ) { + state.siteName = await input.showInputBox({ + title, + step: 2, + totalSteps: 2, + value: state.siteName || siteName, + placeholder: vscode.l10n.t(SITE_NAME), + validate: async (value) => (value ? undefined : vscode.l10n.t(SITE_NAME_REQUIRED)), + }); + } + + const siteInputState = await collectInputs(); + // Return the collected site creation inputs including site name, environment name, and domain name + return siteInputState; +} diff --git a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteTypes.ts b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteTypes.ts index 30d8f358..6d43ea2e 100644 --- a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteTypes.ts +++ b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteTypes.ts @@ -5,9 +5,10 @@ import { ITelemetry } from "../../../../OneDSLoggerTelemetry/telemetry/ITelemetry"; import * as vscode from 'vscode'; +import { IIntelligenceAPIEndpointInformation } from "../../../../services/Interfaces"; export interface ICreateSiteOptions { - intelligenceEndpoint: string; + intelligenceAPIEndpointInfo: IIntelligenceAPIEndpointInformation; intelligenceApiToken: string; userPrompt: string; sessionId: string; @@ -31,3 +32,13 @@ export interface IPreviewSitePagesContentOptions { envId: string; userId: string; } + +export interface ISiteInputState { + siteName: string; + envName: string; + orgUrl: string; + domainName: string; + title: string; + step: number; + totalSteps: number; +} diff --git a/src/common/constants.ts b/src/common/constants.ts index dfb5f8fb..b7c36538 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -85,6 +85,11 @@ export interface IApiRequestParams { relatedFiles?: IRelatedFiles[]; } +export interface IEnvInfo { + orgUrl: string; + envDisplayName: string; +} + export const VSCODE_EXTENSION_COPILOT_CONTEXT_RELATED_FILES_FETCH_FAILED = "VSCodeExtensionCopilotContextRelatedFilesFetchFailed"; export const ADX_WEBPAGE = 'adx_webpage' export const HTML_FILE_EXTENSION = '.html'; diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index 4adfdeb3..9fd14b33 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -426,6 +426,10 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { sendTelemetryEvent(this.telemetry, { eventName: CopilotOrgChangedEvent, copilotSessionId: sessionID, orgId: orgID }); const intelligenceAPIEndpointInfo = await ArtemisService.getIntelligenceEndpoint(orgID, this.telemetry, sessionID, environmentId); + if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { + this.sendMessageToWebview({ type: 'Unavailable' }); + return; + } this.aibEndpoint = intelligenceAPIEndpointInfo.intelligenceEndpoint; this.geoName = intelligenceAPIEndpointInfo.geoName; this.crossGeoDataMovementEnabledPPACFlag = intelligenceAPIEndpointInfo.crossGeoDataMovementEnabledPPACFlag; diff --git a/src/common/services/ArtemisService.ts b/src/common/services/ArtemisService.ts index 9410e853..10c623a1 100644 --- a/src/common/services/ArtemisService.ts +++ b/src/common/services/ArtemisService.ts @@ -21,7 +21,9 @@ export class ArtemisService { if (artemisResponse === null) { return { intelligenceEndpoint: null, geoName: null, crossGeoDataMovementEnabledPPACFlag: false }; } - const { geoName, environment, clusterNumber } = artemisResponse.response as unknown as IArtemisAPIOrgResponse; + + const endpointStamp = artemisResponse.stamp; + const { geoName, environment, clusterNumber } = artemisResponse.response as IArtemisAPIOrgResponse; sendTelemetryEvent(telemetry, { eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); const crossGeoDataMovementEnabledPPACFlag = await BAPService.getCrossGeoCopilotDataMovementEnabledFlag(artemisResponse.stamp, telemetry, environmentId); @@ -38,7 +40,7 @@ export class ArtemisService { const intelligenceEndpoint = `https://aibuildertextapiservice.${geoName}-${'il' + clusterNumber}.gateway.${environment}.island.powerapps.com/v1.0/${orgId}/appintelligence/chat` - return { intelligenceEndpoint: intelligenceEndpoint, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag }; + return { intelligenceEndpoint: intelligenceEndpoint, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag, endpointStamp: endpointStamp }; } // Function to fetch Artemis response diff --git a/src/common/services/Interfaces.ts b/src/common/services/Interfaces.ts index e32457a3..49a7030a 100644 --- a/src/common/services/Interfaces.ts +++ b/src/common/services/Interfaces.ts @@ -25,15 +25,11 @@ export interface IArtemisAPIOrgResponse { clusterType: string, } -export interface IArtemisServiceResponse { - stamp: ServiceEndpointCategory; - response: IArtemisAPIOrgResponse; -} - export interface IIntelligenceAPIEndpointInformation { intelligenceEndpoint: string | null, geoName: string | null, - crossGeoDataMovementEnabledPPACFlag: boolean + crossGeoDataMovementEnabledPPACFlag: boolean, + endpointStamp?: ServiceEndpointCategory, } export interface IWebsiteDetails { diff --git a/src/common/utilities/Utils.ts b/src/common/utilities/Utils.ts index 0bbe584c..c53080eb 100644 --- a/src/common/utilities/Utils.ts +++ b/src/common/utilities/Utils.ts @@ -4,7 +4,7 @@ */ import * as vscode from "vscode"; -import { componentTypeSchema, EXTENSION_ID, EXTENSION_NAME, IRelatedFiles, relatedFilesSchema, SETTINGS_EXPERIMENTAL_STORE_NAME, VSCODE_EXTENSION_COPILOT_CONTEXT_RELATED_FILES_FETCH_FAILED } from "../constants"; +import { componentTypeSchema, EXTENSION_ID, EXTENSION_NAME, IEnvInfo, IRelatedFiles, relatedFilesSchema, SETTINGS_EXPERIMENTAL_STORE_NAME, VSCODE_EXTENSION_COPILOT_CONTEXT_RELATED_FILES_FETCH_FAILED } from "../constants"; import { CUSTOM_TELEMETRY_FOR_POWER_PAGES_SETTING_NAME } from "../OneDSLoggerTelemetry/telemetryConstants"; import { COPILOT_UNAVAILABLE, DataverseEntityNameMap, EntityFieldMap, FieldTypeMap } from "../copilot/constants"; import { IActiveFileData } from "../copilot/model"; @@ -325,8 +325,11 @@ export function getECSOrgLocationValue(clusterName: string, clusterNumber: strin } //API call to get env list for an org -export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEndpointCategory): Promise<{ envId: string, envDisplayName: string }[]> { - const envInfo: { envId: string, envDisplayName: string }[] = []; +export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEndpointCategory | undefined): Promise { + if(!endpointStamp) { + return []; + } + const envInfo: IEnvInfo[] = []; try { const bapAuthToken = await bapServiceAuthentication(telemetry, true); const bapEndpoint = getBAPEndpoint(endpointStamp, telemetry); @@ -344,7 +347,7 @@ export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEn // eslint-disable-next-line @typescript-eslint/no-explicit-any envListJson.value.forEach((env: any) => { envInfo.push({ - envId: env.properties.linkedEnvironmentMetadata.instanceUrl, + orgUrl: env.properties.linkedEnvironmentMetadata.instanceUrl, envDisplayName: env.properties.displayName }); });