Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PowerPages][create-site] Preview and Edit Site Page and Command Registration #1061

Merged
merged 15 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/common/chat-participants/ChatParticipantUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
*/

import * as vscode from 'vscode';
import { Command, CommandRegistry } from './CommandRegistry';

export function createChatParticipant(participantId: string, handler: vscode.ChatRequestHandler): vscode.ChatParticipant {
return vscode.chat.createChatParticipant(participantId, handler);
}

export function registerCommands(commandRegistry: CommandRegistry, commands: { [key: string]: Command }) {
for (const commandName in commands) {
amitjoshi438 marked this conversation as resolved.
Show resolved Hide resolved
commandRegistry.register(commandName, commands[commandName]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
*/

import * as vscode from 'vscode';
import { createChatParticipant } from '../ChatParticipantUtils';
import { createChatParticipant, registerCommands } from '../ChatParticipantUtils';
import { IComponentInfo, IPowerPagesChatResult } from './PowerPagesChatParticipantTypes';
import { ITelemetry } from "../../OneDSLoggerTelemetry/telemetry/ITelemetry";
import TelemetryReporter from '@vscode/extension-telemetry';
import { sendApiRequest } from '../../copilot/IntelligenceApiService';
import { PacWrapper } from '../../../client/pac/PacWrapper';
import { intelligenceAPIAuthentication } from '../../services/AuthenticationProvider';
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, RESPONSE_AWAITED_MSG, RESPONSE_SCENARIOS, SKIP_CODES, STATER_PROMPTS, WELCOME_MESSAGE, WELCOME_PROMPT } from './PowerPagesChatParticipantConstants';
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 { checkCopilotAvailability, fetchRelatedFiles, getActiveEditorContent } from '../../utilities/Utils';
Expand All @@ -25,10 +25,6 @@ import { oneDSLoggerWrapper } from '../../OneDSLoggerTelemetry/oneDSLoggerWrappe
import { CommandRegistry } from '../CommandRegistry';
import { VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_INVOKED, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_ORG_DETAILS_NOT_FOUND, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_ORG_DETAILS, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_NOT_AVAILABLE_ECS, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_SUCCESSFUL_PROMPT, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_WELCOME_PROMPT, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_NO_PROMPT, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_LOCATION_REFERENCED, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_WEBPAGE_RELATED_FILES, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_SCENARIO, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_ERROR, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_COMMAND_TRIGGERED } from './PowerPagesChatParticipantTelemetryConstants';

// Initialize Command Registry and Register Commands
const commandRegistry = new CommandRegistry();
//Register Commands

export class PowerPagesChatParticipant {
private static instance: PowerPagesChatParticipant | null = null;
private chatParticipant: vscode.ChatParticipant;
Expand Down Expand Up @@ -98,6 +94,10 @@ export class PowerPagesChatParticipant {
this.telemetry.sendTelemetryEvent(VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_INVOKED, { sessionId: this.powerPagesAgentSessionId });
oneDSLoggerWrapper.getLogger().traceInfo(VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_INVOKED, { sessionId: this.powerPagesAgentSessionId });

const commandRegistry = new CommandRegistry();

registerCommands(commandRegistry, POWERPAGES_COMMANDS);

if (!this.isOrgDetailsInitialized) {
stream.progress(PAC_AUTH_INPUT);
await this.initializeOrgDetails();
Expand Down Expand Up @@ -174,7 +174,8 @@ export class PowerPagesChatParticipant {
telemetry: this.telemetry,
orgID: this.orgID,
envID: this.environmentID,
userId: userId
userId: userId,
extensionContext: this.extensionContext
};

return await command.execute(commandRequest, stream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import * as vscode from 'vscode';
import { ADX_ENTITYFORM, ADX_ENTITYLIST } from '../../copilot/constants';
import { CreateSiteCommand } from './commands/create-site/CreateSiteCommand';

// Constants
export const POWERPAGES_CHAT_PARTICIPANT_ID = 'powerpages';
Expand Down Expand Up @@ -58,3 +59,6 @@ export const NL2PAGE_GENERATING_WEBPAGES = vscode.l10n.t("Generating webpages...
export const NL2PAGE_RESPONSE_FAILED = 'Failed to get page content from NL2Page service';
export const NL2SITE_GENERATING_SITE = vscode.l10n.t("Generating a new Power Pages site...");
export const FAILED_TO_CREATE_SITE = vscode.l10n.t('Failed to create a new Power Pages site. Please try again.');
export const POWERPAGES_COMMANDS = {
'create-site': new CreateSiteCommand()
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ export const VSCODE_EXTENSION_CREATE_SITE_COMMAND_FAILED = 'VSCodeExtensionNL2Si
export const VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_COMMAND_TRIGGERED = 'VSCodeExtensionGitHubPowerPagesAgentCommandTriggered';
export const VSCODE_EXTENSION_NL2PAGE_REQUEST = 'VSCodeExtensionNL2PageRequest';
export const VSCODE_EXTENSION_NL2SITE_REQUEST = 'VSCodeExtensionNL2SiteRequest';
export const VSCODE_EXTENSION_PREVIEW_SITE_PAGES = 'VSCodeExtensionPreviewSitePages';
export const VSCODE_EXTENSION_PREVIEW_SITE_PAGES_ERROR = 'VSCodeExtensionPreviewSitePagesError';
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,24 @@ import { VSCODE_EXTENSION_CREATE_SITE_COMMAND_FAILED} from "../../PowerPagesChat

export class CreateSiteCommand implements Command {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async execute(request: any, stream: vscode.ChatResponseStream): Promise<any> {
const { prompt, intelligenceAPIEndpointInfo, intelligenceApiToken, powerPagesAgentSessionId, telemetry, orgId, envId, userId } = request;
async execute(requestObject: any, stream: vscode.ChatResponseStream): Promise<any> {
const { request, intelligenceAPIEndpointInfo, intelligenceApiToken, powerPagesAgentSessionId, telemetry, orgID, envID, userId, extensionContext } = requestObject;
amitjoshi438 marked this conversation as resolved.
Show resolved Hide resolved

stream.progress(NL2SITE_GENERATING_SITE);
try {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const result = await createSite(
intelligenceAPIEndpointInfo.intelligenceEndpoint,
const result = await createSite({
intelligenceEndpoint: intelligenceAPIEndpointInfo.intelligenceEndpoint,
intelligenceApiToken,
prompt,
powerPagesAgentSessionId,
userPrompt: request.prompt,
sessionId: powerPagesAgentSessionId,
stream,
telemetry,
orgId,
envId,
userId
);
orgId: orgID,
envId: envID,
userId,
extensionContext
});
// Process the result

return {
Expand All @@ -38,8 +39,8 @@ export class CreateSiteCommand implements Command {
};
} catch (error) {
stream.markdown(FAILED_TO_CREATE_SITE);
telemetry.sendTelemetryEvent(VSCODE_EXTENSION_CREATE_SITE_COMMAND_FAILED, { sessionId: powerPagesAgentSessionId, orgId:orgId, envId: envId, userId: userId, error: error as string });
oneDSLoggerWrapper.getLogger().traceError(VSCODE_EXTENSION_CREATE_SITE_COMMAND_FAILED, error as string, error as Error, { sessionId: powerPagesAgentSessionId, orgId:orgId, envId: envId, userId: userId}, {});
telemetry.sendTelemetryEvent(VSCODE_EXTENSION_CREATE_SITE_COMMAND_FAILED, { sessionId: powerPagesAgentSessionId, orgId:orgID, envId: envID, userId: userId, error: error as string });
oneDSLoggerWrapper.getLogger().traceError(VSCODE_EXTENSION_CREATE_SITE_COMMAND_FAILED, error as string, error as Error, { sessionId: powerPagesAgentSessionId, orgId:orgID, envId: envID, userId: userId}, {});
return {
metadata: {
command: ''
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

export const EDITABLE_SCHEME = 'editable';
export const ENGLISH = "English";
export const MIN_PAGES = 7;
export const MAX_PAGES = 7;
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,32 @@ import { getNL2PageData } from './Nl2PageService';
import { getNL2SiteData } from './Nl2SiteService';
import { NL2SITE_REQUEST_FAILED, NL2PAGE_GENERATING_WEBPAGES, NL2PAGE_RESPONSE_FAILED } from '../../PowerPagesChatParticipantConstants';
import { oneDSLoggerWrapper } from '../../../../OneDSLoggerTelemetry/oneDSLoggerWrapper';
import { VSCODE_EXTENSION_NL2PAGE_REQUEST, VSCODE_EXTENSION_NL2SITE_REQUEST } from '../../PowerPagesChatParticipantTelemetryConstants';
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';

export const createSite = async (intelligenceEndpoint: string, intelligenceApiToken: string, userPrompt: string, sessionId: string, stream: vscode.ChatResponseStream, telemetry: ITelemetry, orgId: string, envID: string, userId: string) => {
const { siteName, siteDescription } = await fetchSiteAndPageData(intelligenceEndpoint, intelligenceApiToken, userPrompt, sessionId, telemetry, stream, orgId, envID, userId);
export const createSite = async (createSiteOptions: ICreateSiteOptions) => {
const {
intelligenceEndpoint,
intelligenceApiToken,
userPrompt,
sessionId,
stream,
telemetry,
orgId,
envId,
userId,
extensionContext
} = createSiteOptions;

const { siteName, siteDescription, sitePages } = await fetchSiteAndPageData(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});

// TODO: Implement the create site button click handler

return {
siteName,
Expand Down Expand Up @@ -46,3 +68,53 @@ async function fetchSiteAndPageData(intelligenceEndpoint: string, intelligenceAp

return { siteName, sitePagesList, sitePages, siteDescription };
}


function previewSitePagesContent(
options: IPreviewSitePagesContentOptions
): EditableFileSystemProvider {
const {
sitePages,
stream,
extensionContext,
telemetry,
sessionId,
orgId,
envId,
userId
} = options;

try {
const sitePagesContent: { name: string; content: string }[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sitePages.forEach((page: any) => {
sitePagesContent.push({ name: page.metadata.pageTitle, content: page.code });
});

const sitePagesFolder: vscode.ChatResponseFileTree[] = [];
const contentProvider = new EditableFileSystemProvider();
// Register the content provider
extensionContext.subscriptions.push(
vscode.workspace.registerFileSystemProvider(EDITABLE_SCHEME, contentProvider, { isCaseSensitive: true })
);

const baseUri = vscode.Uri.parse(`${EDITABLE_SCHEME}:/`);

sitePagesContent.forEach((page: { name: string; content: string; }) => {
sitePagesFolder.push({ name: page.name + HTML_FILE_EXTENSION });
const pageUri = vscode.Uri.joinPath(baseUri, page.name + HTML_FILE_EXTENSION);
contentProvider.writeFile(pageUri, Buffer.from(page.content, UTF8_ENCODING));
});

telemetry.sendTelemetryEvent(VSCODE_EXTENSION_PREVIEW_SITE_PAGES, { sessionId, orgId, environmentId: envId, userId });

stream.filetree(sitePagesFolder, baseUri);

return contentProvider;
} catch (error) {
telemetry.sendTelemetryEvent(VSCODE_EXTENSION_PREVIEW_SITE_PAGES_ERROR, { sessionId, orgId, environmentId: envId, userId, error: (error as Error).message });
oneDSLoggerWrapper.getLogger().traceError(VSCODE_EXTENSION_PREVIEW_SITE_PAGES_ERROR, (error as Error).message, error as Error, { sessionId, orgId, environmentId: envId, userId }, {});
throw error;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { ITelemetry } from "../../../../OneDSLoggerTelemetry/telemetry/ITelemetry";
import * as vscode from 'vscode';

export interface ICreateSiteOptions {
intelligenceEndpoint: string;
intelligenceApiToken: string;
userPrompt: string;
sessionId: string;
stream: vscode.ChatResponseStream;
telemetry: ITelemetry;
orgId: string;
envId: string;
userId: string;
extensionContext: vscode.ExtensionContext;
}

export interface IPreviewSitePagesContentOptions {
// siteName: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sitePages: any[];
stream: vscode.ChatResponseStream;
extensionContext: vscode.ExtensionContext;
telemetry: ITelemetry;
sessionId: string;
orgId: string;
envId: string;
userId: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NL2SITE_GENERATE_NEW_SITE, NL2SITE_INVALID_RESPONSE, NL2SITE_SCENARIO}
import {VSCODE_EXTENSION_NL2SITE_REQUEST_FAILED, VSCODE_EXTENSION_NL2SITE_REQUEST_SUCCESS } from "../../PowerPagesChatParticipantTelemetryConstants";
import { getCommonHeaders } from "../../../../services/AuthenticationProvider";
import { oneDSLoggerWrapper } from "../../../../OneDSLoggerTelemetry/oneDSLoggerWrapper";
import { ENGLISH, MAX_PAGES, MIN_PAGES } from "./CreateSiteConstants";

export async function getNL2SiteData(aibEndpoint: string, aibToken: string, userPrompt: string, sessionId: string, telemetry: ITelemetry, orgId: string, envId: string, userId: string) {
const requestBody = {
Expand All @@ -22,8 +23,10 @@ export async function getNL2SiteData(aibEndpoint: string, aibToken: string, user
// "shouldCheckBlockList": false, //TODO: Check if this is needed
"version": "V1",
"information": {
"minPages": 7,
"maxPages": 7
"minPages": MIN_PAGES,
"maxPages": MAX_PAGES,
"language": ENGLISH

}
}
};
Expand Down
2 changes: 2 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,5 @@ export interface IApiRequestParams {

export const VSCODE_EXTENSION_COPILOT_CONTEXT_RELATED_FILES_FETCH_FAILED = "VSCodeExtensionCopilotContextRelatedFilesFetchFailed";
export const ADX_WEBPAGE = 'adx_webpage'
export const HTML_FILE_EXTENSION = '.html';
export const UTF8_ENCODING = 'utf8';
65 changes: 65 additions & 0 deletions src/common/utilities/EditableFileSystemProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

/* eslint-disable @typescript-eslint/no-unused-vars */

import * as vscode from 'vscode';


export class EditableFileSystemProvider implements vscode.FileSystemProvider {
private _fileContentMap: { [key: string]: Uint8Array } = {};
private _onDidChangeEmitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
readonly onDidChangeFile = this._onDidChangeEmitter.event;

watch(uri: vscode.Uri, options: { readonly recursive: boolean; readonly excludes: readonly string[]; }): vscode.Disposable {
// For simplicity, this implementation does not support file watching.
// eslint-disable-next-line @typescript-eslint/no-empty-function
return new vscode.Disposable(() => {});
}

copy(source: vscode.Uri, destination: vscode.Uri, options: { readonly overwrite: boolean; }): void | Thenable<void> {
// Copy is not supported in this implementation
}

// Read file content
readFile(uri: vscode.Uri): Uint8Array {
const filePath = uri.path;
return this._fileContentMap[filePath] || new Uint8Array();
}

// Write file content
writeFile(uri: vscode.Uri, content: Uint8Array): void {
const filePath = uri.path;
this._fileContentMap[filePath] = content;
this._onDidChangeEmitter.fire([{ type: vscode.FileChangeType.Changed, uri }]);
}

// Other required methods for FileSystemProvider
stat(uri: vscode.Uri): vscode.FileStat {
return { type: vscode.FileType.File, ctime: Date.now(), mtime: Date.now(), size: this._fileContentMap[uri.path]?.length || 0 };
}

readDirectory(uri: vscode.Uri): [string, vscode.FileType][] {
return [];
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
createDirectory(uri: vscode.Uri): void {}

delete(uri: vscode.Uri): void {
// Delete is not supported in this implementation
}

rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { readonly overwrite: boolean; }): void {
// Rename is not supported in this implementation
}

// Method to get file content as string
getFileContent(uri: vscode.Uri): string {
const filePath = uri.path;
const content = this._fileContentMap[filePath];
return content ? Buffer.from(content).toString('utf8') : '';
}
}