Skip to content

Commit

Permalink
[@powerpages Copilot] Context Improvement for Webpage (#1016)
Browse files Browse the repository at this point in the history
* Context improvement for webpage

* refactor: Remove commented code for fetching related files

* refactor: Iapi version upgrade

* refactor: Update fetchRelatedFiles to include telemetry for error handling

* refactor: sessionId for relatedFiles

---------

Co-authored-by: amitjoshi <[email protected]>
  • Loading branch information
amitjoshi438 and amitjoshi authored Aug 20, 2024
1 parent ffe2bca commit daffb67
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import { ActiveOrgOutput } from '../../../client/pac/PacTypes';
import { AUTHENTICATION_FAILED_MSG, COPILOT_NOT_AVAILABLE_MSG, DISCLAIMER_MESSAGE, NO_PROMPT_MESSAGE, PAC_AUTH_NOT_FOUND, POWERPAGES_CHAT_PARTICIPANT_ID, RESPONSE_AWAITED_MSG, SKIP_CODES, STATER_PROMPTS, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_INVOKED, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_ORG_DETAILS, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_ORG_DETAILS_NOT_FOUND, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_SCENARIO, WELCOME_MESSAGE, WELCOME_PROMPT } from './PowerPagesChatParticipantConstants';
import { ORG_DETAILS_KEY, handleOrgChangeSuccess, initializeOrgDetails } from '../../utilities/OrgHandlerUtils';
import { createAndReferenceLocation, getComponentInfo, getEndpoint, provideChatParticipantFollowups } from './PowerPagesChatParticipantUtils';
import { checkCopilotAvailability, getActiveEditorContent } from '../../utilities/Utils';
import { checkCopilotAvailability, fetchRelatedFiles, getActiveEditorContent } from '../../utilities/Utils';
import { IIntelligenceAPIEndpointInformation } from '../../services/Interfaces';
import { v4 as uuidv4 } from 'uuid';
import { orgChangeErrorEvent, orgChangeEvent } from '../../../client/OrgChangeNotifier';
import { IRelatedFiles } from '../../constants';

export class PowerPagesChatParticipant {
private static instance: PowerPagesChatParticipant | null = null;
Expand Down Expand Up @@ -159,9 +160,23 @@ export class PowerPagesChatParticipant {
//TODO: Handle command scenarios

} else {
const relatedFiles: IRelatedFiles[] = [];

// Based on dataverse entity fetch required context for the active file
switch (activeFileParams.dataverseEntity) {
case 'adx_webpage':
if (activeFileUri) {
const files = await fetchRelatedFiles(activeFileUri, activeFileParams.dataverseEntity, activeFileParams.fieldType, this.telemetry, this.powerPagesAgentSessionId);
relatedFiles.push(...files);
}
break;
default:
break;
}

const { componentInfo, entityName }: IComponentInfo = await getComponentInfo(this.telemetry, this.orgUrl, activeFileParams, this.powerPagesAgentSessionId);

const llmResponse = await sendApiRequest([{ displayText: userPrompt, code: activeFileContent }], activeFileParams, this.orgID, intelligenceApiToken, this.powerPagesAgentSessionId, entityName, componentInfo, this.telemetry, intelligenceAPIEndpointInfo.intelligenceEndpoint, intelligenceAPIEndpointInfo.geoName, intelligenceAPIEndpointInfo.crossGeoDataMovementEnabledPPACFlag);
const llmResponse = await sendApiRequest([{ displayText: userPrompt, code: activeFileContent }], activeFileParams, this.orgID, intelligenceApiToken, this.powerPagesAgentSessionId, entityName, componentInfo, this.telemetry, intelligenceAPIEndpointInfo.intelligenceEndpoint, intelligenceAPIEndpointInfo.geoName, intelligenceAPIEndpointInfo.crossGeoDataMovementEnabledPPACFlag, relatedFiles);

const scenario = llmResponse.length > 1 ? llmResponse[llmResponse.length - 1] : llmResponse[0].displayText;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,3 @@ export function provideChatParticipantFollowups(result: IPowerPagesChatResult, _
];
}
}

27 changes: 27 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,30 @@ export const PORTAL_YEOMAN_GENERATOR_PACKAGE_TARBALL_NAME = "microsoft-generator


export const SUCCESS = "Success";

// Define the schema for file extensions
export const componentTypeSchema: { [key: string]: { [key: string]: string } } = {
'adx_webpage': {
'html': '.copy.html',
'js': '.custom_javascript.js',
'css': '.custom_css.css'
}
};

// Define the schema
export const relatedFilesSchema: { [key: string]: { [key: string]: string[] } } = {
'adx_webpage': {
'html': ['js', 'css'],
'js': ['html', 'css'],
'css': ['html', 'js']
}
};

// Interface for related files
export interface IRelatedFiles {
fileType: string;
fileContent: string;
fileName: string;
}

export const COPILOT_RELATED_FILES_FETCH_FAILED = "CopilotRelatedFilesFetchFailed";
9 changes: 5 additions & 4 deletions src/common/copilot/IntelligenceApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@
*/

import fetch, { RequestInit } from "node-fetch";
import { INAPPROPRIATE_CONTENT, INPUT_CONTENT_FILTERED, INVALID_INFERENCE_INPUT, InvalidResponse, MalaciousScenerioResponse, NetworkError, PROMPT_LIMIT_EXCEEDED, PromptLimitExceededResponse, RELEVANCY_CHECK_FAILED, RateLimitingResponse, UnauthorizedResponse, UserPrompt } from "./constants";
import { INAPPROPRIATE_CONTENT, INPUT_CONTENT_FILTERED, INVALID_INFERENCE_INPUT,InvalidResponse, MalaciousScenerioResponse, NetworkError, PROMPT_LIMIT_EXCEEDED, PromptLimitExceededResponse, RELEVANCY_CHECK_FAILED, RateLimitingResponse, UnauthorizedResponse, UserPrompt } from "./constants";
import { IActiveFileParams } from "./model";
import { sendTelemetryEvent } from "./telemetry/copilotTelemetry";
import { ITelemetry } from "../OneDSLoggerTelemetry/telemetry/ITelemetry";
import { CopilotResponseFailureEvent, CopilotResponseFailureEventWithMessage, CopilotResponseOkFailureEvent, CopilotResponseSuccessEvent } from "./telemetry/telemetryConstants";
import { getExtensionType, getExtensionVersion } from "../utilities/Utils";
import { EXTENSION_NAME } from "../constants";
import { EXTENSION_NAME, IRelatedFiles } from "../constants";
import { enableCrossGeoDataFlowInGeo } from "./utils/copilotUtil";

const clientType = EXTENSION_NAME + '-' + getExtensionType();
const clientVersion = getExtensionVersion();

export async function sendApiRequest(userPrompt: UserPrompt[], activeFileParams: IActiveFileParams, orgID: string, apiToken: string, sessionID: string, entityName: string, entityColumns: string[], telemetry: ITelemetry, aibEndpoint: string | null, geoName: string | null, crossGeoDataMovementEnabledPPACFlag = false) {
export async function sendApiRequest(userPrompt: UserPrompt[], activeFileParams: IActiveFileParams, orgID: string, apiToken: string, sessionID: string, entityName: string, entityColumns: string[], telemetry: ITelemetry, aibEndpoint: string | null, geoName: string | null, crossGeoDataMovementEnabledPPACFlag = false, RelatedFiles?: IRelatedFiles[]) {

if (!aibEndpoint) {
return NetworkError;
Expand All @@ -30,7 +30,7 @@ export async function sendApiRequest(userPrompt: UserPrompt[], activeFileParams:
"sessionId": sessionID,
"scenario": "PowerPagesProDev",
"subScenario": "PowerPagesProDevGeneric",
"version": "V1",
"version": "V2",
"information": {
"dataverseEntity": activeFileParams.dataverseEntity,
"entityField": activeFileParams.entityField,
Expand All @@ -40,6 +40,7 @@ export async function sendApiRequest(userPrompt: UserPrompt[], activeFileParams:
"targetColumns": entityColumns,
"clientType": clientType,
"clientVersion": clientVersion,
"RelatedFiles": RelatedFiles ? RelatedFiles : [{ fileType: '', fileContent: '', fileName: '' }]
}
},
"crossGeoOptions": {
Expand Down
5 changes: 3 additions & 2 deletions src/common/copilot/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ export const EntityFieldMap = new Map<string, string>([
]);

export const FieldTypeMap = new Map<string, string>([
['js', 'JavaScript'],
['html', 'html']
['js', 'js'],
['html', 'html'],
['css', 'css']
]);

export const ControlClassIdMap = new Map<string, string>([
Expand Down
105 changes: 93 additions & 12 deletions src/common/utilities/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
*/

import * as vscode from "vscode";
import { EXTENSION_ID, EXTENSION_NAME, SETTINGS_EXPERIMENTAL_STORE_NAME } from "../constants";
import { componentTypeSchema, COPILOT_RELATED_FILES_FETCH_FAILED, EXTENSION_ID, EXTENSION_NAME, IRelatedFiles, relatedFilesSchema, SETTINGS_EXPERIMENTAL_STORE_NAME } 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";
import { ITelemetry } from "../OneDSLoggerTelemetry/telemetry/ITelemetry";
import { sendTelemetryEvent } from "../copilot/telemetry/copilotTelemetry";
import { getDisabledOrgList, getDisabledTenantList } from "../copilot/utils/copilotUtil";
import { CopilotNotAvailable, CopilotNotAvailableECSConfig } from "../copilot/telemetry/telemetryConstants";
import path from "path";

export function getSelectedCode(editor: vscode.TextEditor): string {
if (!editor) {
Expand Down Expand Up @@ -146,18 +147,17 @@ export function getActiveEditorContent(): IActiveFileData {
return { activeFileContent: '', startLine: 0, endLine: 0, activeFileUri: undefined, activeFileParams: { dataverseEntity: '', entityField: '', fieldType: '' } };
}

const document = activeEditor.document;
const fileName = document.fileName;
const relativeFileName = vscode.workspace.asRelativePath(fileName);
const activeFileUri = document.uri;
const activeFileParams: string[] = getLastThreePartsOfFileName(relativeFileName);
const document = activeEditor.document,
fileName = document.fileName,
relativeFileName = vscode.workspace.asRelativePath(fileName),
activeFileUri = document.uri,
activeFileParams: string[] = getLastThreePartsOfFileName(relativeFileName),
selectedCode = getSelectedCode(activeEditor),
selectedCodeLineRange = getSelectedCodeLineRange(activeEditor);

let activeFileContent = document.getText();
let startLine = 0;
let endLine = document.lineCount;

const selectedCode = getSelectedCode(activeEditor);
const selectedCodeLineRange = getSelectedCodeLineRange(activeEditor);
let activeFileContent = document.getText(),
startLine = 0,
endLine = document.lineCount;

if (selectedCode.length > 0) {
activeFileContent = selectedCode;
Expand Down Expand Up @@ -219,3 +219,84 @@ export function getVisibleCode(editor: vscode.TextEditor): { code: string; start
endLine: firstVisibleRange.end.line
};
}


async function getFileContent(activeFileUri: vscode.Uri, customExtension: string): Promise<{ customFileContent: string; customFileName: string; }> {
try {
const activeFileFolderPath = getFolderPathFromUri(activeFileUri);
const activeFileName = getFileNameFromUri(activeFileUri);

const activeFileNameParts = activeFileName.split('.');

let customFileName = activeFileNameParts[0];

for (let i = 1; i < activeFileNameParts.length - 2; i++) {
customFileName += `.${activeFileNameParts[i]}`;
}

customFileName += customExtension;

const customFilePath = path.join(activeFileFolderPath, customFileName);

// Read the content of the custom file
const diskRead = await import('fs');
const customFileContent = diskRead.readFileSync(customFilePath, 'utf8');

return { customFileContent, customFileName };
} catch (error) {
throw new Error(`Error reading the custom file content: ${error}`);
}
}

// Generic function to get file content based on type and component type
async function getFileContentByType(activeFileUri: vscode.Uri, componentType: string, fileType: string): Promise<{ customFileContent: string; customFileName: string; }> {
try {
const extension = componentTypeSchema[componentType]?.[fileType];
if (!extension) {
throw new Error(`File type ${fileType} not found for component type ${componentType}`);
}
return await getFileContent(activeFileUri, extension);
} catch (error) {
const message = (error as Error)?.message;
throw new Error(message);
}
}

//fetchRelatedFiles function based on component type
export async function fetchRelatedFiles(activeFileUri: vscode.Uri, componentType: string, fieldType: string, telemetry: ITelemetry, sessionId:string): Promise<IRelatedFiles[]> {
try {
const relatedFileTypes = relatedFilesSchema[componentType]?.[fieldType];
if (!relatedFileTypes) {
return [];
}

const files: IRelatedFiles[] = await Promise.all(
relatedFileTypes.map(async fileType => {
const fileContentResult = await getFileContentByType(activeFileUri, componentType, fileType);
return {
fileType,
fileContent: fileContentResult.customFileContent,
fileName: fileContentResult.customFileName
};
})
);

return files;
} catch (error) {
const message = (error as Error)?.message;
telemetry.sendTelemetryErrorEvent(COPILOT_RELATED_FILES_FETCH_FAILED, { error: message, sessionId: sessionId });
return [];
}
}

export function getFilePathFromUri(uri: vscode.Uri): string {
return uri.fsPath;
}

export function getFileNameFromUri(uri: vscode.Uri): string {
return path.basename(uri.fsPath);
}

export function getFolderPathFromUri(uri: vscode.Uri): string {
return path.dirname(uri.fsPath);
}

0 comments on commit daffb67

Please sign in to comment.