Skip to content

Commit

Permalink
Users/amitjoshi/gpt tokenizer integration (#734)
Browse files Browse the repository at this point in the history
* getting info about selected code

* showing label for selected lines of code

* add flag to disable feature

* updated responsiveness

* code formatting fix

* removed log statement

* handles empty selections

* passing and showing user selected code to copilot

* setting fixed vertical height for user code

* removed redundant code

* added comment

* added const for explainCode msg

* sending localized string to copilot webview

* gpt-tokenizer integration

* user prompt with selected code and 'explain' check

* showing context cmd only when copilot  registered

* update handling of empty snippet

* updated when clause

* moved code to skip in a const

* Added telemetry

* updated strings

* user initial fix

---------

Co-authored-by: amitjoshi <[email protected]>
Co-authored-by: tyaginidhi <[email protected]>
  • Loading branch information
3 people authored Oct 17, 2023
1 parent 6155dc9 commit 525a0d2
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 22 deletions.
18 changes: 16 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@
{
"submenu": "microsoft-powerapps-portals.powerpages-copilot",
"group": "0_powerpages-copilot",
"when": "never"
"when": "(powerpages.copilot.isVisible) && ((!virtualWorkspace && powerpages.websiteYmlExists && config.powerPlatform.experimental.copilotEnabled) || (isWeb && config.powerPlatform.experimental.enableWebCopilot))"
}
],
"microsoft-powerapps-portals.powerpages-copilot": [
Expand Down Expand Up @@ -1033,6 +1033,7 @@
"command-exists": "^1.2.9",
"find-process": "^1.4.7",
"glob": "^7.1.7",
"gpt-tokenizer": "^2.1.1",
"liquidjs": "^10.2.0",
"n-readlines": "^1.0.1",
"puppeteer-core": "^14.4.1",
Expand Down
8 changes: 4 additions & 4 deletions src/common/copilot/IntelligenceApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

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 } 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 "../../client/telemetry/ITelemetry";
Expand All @@ -15,14 +15,14 @@ import { EXTENSION_NAME } from "../../client/constants";
const clientType = EXTENSION_NAME + '-' + getExtensionType();
const clientVersion = getExtensionVersion();

export async function sendApiRequest(userPrompt: string, activeFileParams: IActiveFileParams, orgID: string, apiToken: string, sessionID: string, entityName: string, entityColumns: string[], telemetry: ITelemetry, aibEndpoint: string | null) {
export async function sendApiRequest(userPrompt: UserPrompt[], activeFileParams: IActiveFileParams, orgID: string, apiToken: string, sessionID: string, entityName: string, entityColumns: string[], telemetry: ITelemetry, aibEndpoint: string | null) {

if (!aibEndpoint) {
return NetworkError;
}

const requestBody = {
"question": userPrompt,
"question": userPrompt[0].displayText,
"top": 1,
"context": {
"sessionId": sessionID,
Expand All @@ -33,7 +33,7 @@ export async function sendApiRequest(userPrompt: string, activeFileParams: IActi
"dataverseEntity": activeFileParams.dataverseEntity,
"entityField": activeFileParams.entityField,
"fieldType": activeFileParams.fieldType,
"activeFileContent": '', //TODO: Add active file content (selected code)
"activeFileContent": userPrompt[0].code, //Active file content (selected code)
"targetEntity": entityName,
"targetColumns": entityColumns,
"clientType": clientType,
Expand Down
27 changes: 20 additions & 7 deletions src/common/copilot/PowerPagesCopilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ import { dataverseAuthentication, intelligenceAPIAuthentication } from "../../we
import { v4 as uuidv4 } from 'uuid'
import { PacWrapper } from "../../client/pac/PacWrapper";
import { ITelemetry } from "../../client/telemetry/ITelemetry";
import { AUTH_CREATE_FAILED, AUTH_CREATE_MESSAGE, AuthProfileNotFound, COPILOT_UNAVAILABLE, CopilotDisclaimer, CopilotStylePathSegments, DataverseEntityNameMap, EXPLAIN_CODE, EntityFieldMap, FieldTypeMap, PAC_SUCCESS, SELECTED_CODE_INFO_ENABLED, WebViewMessage, sendIconSvg } from "./constants";
import { AUTH_CREATE_FAILED, AUTH_CREATE_MESSAGE, AuthProfileNotFound, COPILOT_UNAVAILABLE, CopilotDisclaimer, CopilotStylePathSegments, DataverseEntityNameMap, EXPLAIN_CODE, EntityFieldMap, FieldTypeMap, PAC_SUCCESS, SELECTED_CODE_INFO, SELECTED_CODE_INFO_ENABLED, UserPrompt, WebViewMessage, sendIconSvg } from "./constants";
import { IActiveFileParams, IActiveFileData, IOrgInfo } from './model';
import { escapeDollarSign, getLastThreePartsOfFileName, getNonce, getSelectedCode, getSelectedCodeLineRange, getUserName, openWalkthrough, showConnectedOrgMessage, showInputBoxAndGetOrgUrl, showProgressWithNotification } from "../Utils";
import { CESUserFeedback } from "./user-feedback/CESSurvey";
import { GetAuthProfileWatchPattern } from "../../client/lib/AuthPanelView";
import { ActiveOrgOutput } from "../../client/pac/PacTypes";
import { CopilotWalkthroughEvent, CopilotCopyCodeToClipboardEvent, CopilotInsertCodeToEditorEvent, CopilotLoadedEvent, CopilotOrgChangedEvent, CopilotUserFeedbackThumbsDownEvent, CopilotUserFeedbackThumbsUpEvent, CopilotUserPromptedEvent, CopilotCodeLineCountEvent, CopilotClearChatEvent, CopilotNotAvailable } from "./telemetry/telemetryConstants";
import { CopilotWalkthroughEvent, CopilotCopyCodeToClipboardEvent, CopilotInsertCodeToEditorEvent, CopilotLoadedEvent, CopilotOrgChangedEvent, CopilotUserFeedbackThumbsDownEvent, CopilotUserFeedbackThumbsUpEvent, CopilotUserPromptedEvent, CopilotCodeLineCountEvent, CopilotClearChatEvent, CopilotNotAvailable, CopilotExplainCode, CopilotExplainCodeSize } from "./telemetry/telemetryConstants";
import { sendTelemetryEvent } from "./telemetry/copilotTelemetry";
import { INTELLIGENCE_SCOPE_DEFAULT, PROVIDER_ID } from "../../web/client/common/constants";
import { getIntelligenceEndpoint } from "../ArtemisService";
import TelemetryReporter from "@vscode/extension-telemetry";
import { getEntityColumns, getEntityName } from "./dataverseMetadata";
import { COPILOT_STRINGS } from "./assets/copilotStrings";
import { isWithinTokenLimit, encode } from "gpt-tokenizer";

let intelligenceApiToken: string;
let userID: string; // Populated from PAC or intelligence API
Expand Down Expand Up @@ -79,18 +80,26 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider {
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!'));
vscode.window.showInformationMessage(vscode.l10n.t('Selection is empty.'));
return;
}
this.sendMessageToWebview({ type: commandType, value: { start: selectedCodeLineRange.start, end: selectedCodeLineRange.end, selectedCode: selectedCode } });
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("selectedCodeInfo"))
vscode.window.onDidChangeTextEditorSelection(() => handleSelectionChange(SELECTED_CODE_INFO)), vscode.window.onDidChangeActiveTextEditor(() => handleSelectionChange(SELECTED_CODE_INFO))
);

this._disposables.push(
vscode.commands.registerCommand("powerpages.copilot.explain", () => handleSelectionChange(EXPLAIN_CODE))
vscode.commands.registerCommand("powerpages.copilot.explain", () => {sendTelemetryEvent(this.telemetry, { eventName: CopilotExplainCode, copilotSessionId: sessionID, orgId: orgID }); this.show(); handleSelectionChange(EXPLAIN_CODE)})
);
}

Expand Down Expand Up @@ -167,6 +176,10 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider {

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) {
Expand Down Expand Up @@ -319,7 +332,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider {
}
}

private async authenticateAndSendAPIRequest(data: string, activeFileParams: IActiveFileParams, orgID: string, telemetry: ITelemetry) {
private async authenticateAndSendAPIRequest(data: UserPrompt[], activeFileParams: IActiveFileParams, orgID: string, telemetry: ITelemetry) {
return intelligenceAPIAuthentication(telemetry, sessionID, orgID)
.then(async ({ accessToken, user, userId }) => {
intelligenceApiToken = accessToken;
Expand Down
3 changes: 2 additions & 1 deletion src/common/copilot/assets/copilotStrings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
import vscode from "vscode";

export const COPILOT_STRINGS = {
EXPLAIN_CODE_PROMPT: vscode.l10n.t('Explain the following code:'),
EXPLAIN_CODE_PROMPT: vscode.l10n.t('Explain the following code snippet:'),
LARGE_SELECTION: vscode.l10n.t('Selection is too large. Try making a shorter selection.'),
}
19 changes: 13 additions & 6 deletions src/common/copilot/assets/scripts/copilot.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
const chatMessages = document.getElementById("chat-messages");
const chatInput = document.getElementById("chat-input");
const chatInputComponent = document.getElementById("input-component");
const skipCodes = ["", null, undefined, "violation", "unclear", "explain"];

let userName;
let apiResponseHandler;
Expand All @@ -24,6 +25,7 @@
let copilotStrings = {};



const inputHistory = [];
let currentIndex = -1;

Expand Down Expand Up @@ -79,7 +81,7 @@
return resultDiv;
}

if (responseText[i].code === "" || responseText[i].code === null || responseText[i].code === undefined || responseText[i].code === "violation" || responseText[i].code === "unclear") {
if (skipCodes.includes(responseText[i].code)) {
continue;
}

Expand Down Expand Up @@ -147,7 +149,7 @@
const nameArray = name.split(" ");
const initials = nameArray.map((word) => word.charAt(0));
const truncatedInitials = initials.slice(0, 2);
return truncatedInitials.join("");
return truncatedInitials.join("").toUpperCase();
}


Expand Down Expand Up @@ -455,7 +457,7 @@
break;
}
case "Available": {
if(isCopilotEnabled== false) {
if(isCopilotEnabled === false) {
isCopilotEnabled = true;
chatInputComponent.classList.remove("hide");
chatMessages.innerHTML = "";
Expand All @@ -467,12 +469,17 @@
case "selectedCodeInfo": {
const chatInputLabel = document.getElementById("input-label-id");
selectedCode = message.value.selectedCode;
if (message.value.start == message.value.end && selectedCode.length == 0) {
if (selectedCode.length === 0) {
chatInputLabel.classList.add("hide");
break;
}
chatInputLabel.classList.remove("hide");
chatInputLabel.innerText = `Lines: ${message.value.start + 1} - ${message.value.end + 1} selected`;
if(message.value.tokenSize === false){
chatInputLabel.innerText = copilotStrings.LARGE_SELECTION;
selectedCode = "";
break;
}
chatInputLabel.innerText = `Lines ${message.value.start + 1} - ${message.value.end + 1} selected`;
break;
}
case "explainCode": {
Expand Down Expand Up @@ -520,7 +527,7 @@
chatInput.disabled = true;
saveInputToHistory(input);
apiResponseInProgress = true;
getApiResponse(input + ': ' + selectedCode); //TODO: userPrompt object should be passed
getApiResponse(userPrompt); //TODO: userPrompt object should be passed
chatInput.value = "";
chatInput.focus();
}
Expand Down
6 changes: 6 additions & 0 deletions src/common/copilot/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const PROMPT_LIMIT_EXCEEDED = 'PromptLimitExceeded';
export const INVALID_INFERENCE_INPUT = 'InvalidInferenceInput';
export const COPILOT_NOTIFICATION_DISABLED = 'isCopilotNotificationDisabled'
export const EXPLAIN_CODE = 'explainCode';
export const SELECTED_CODE_INFO = "selectedCodeInfo";
export const SELECTED_CODE_INFO_ENABLED = false;

export type WebViewMessage = {
Expand All @@ -35,6 +36,11 @@ export type WebViewMessage = {
envName?: string;
};

export interface UserPrompt {
displayText: string;
code: string;
}

export const DataverseEntityNameMap = new Map<string, string>([
['webpage', 'adx_webpage'],
['list', 'adx_entitylist'],
Expand Down
1 change: 1 addition & 0 deletions src/common/copilot/telemetry/ITelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export interface IProDevCopilotTelemetryData {
geoName?: string,
aibEndpoint?: string,
orgUrl?: string,
tokenSize?: string
}
3 changes: 2 additions & 1 deletion src/common/copilot/telemetry/copilotTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export function sendTelemetryEvent(telemetry: ITelemetry, telemetryData: IProDev
telemetryDataProperties.FeedbackId = telemetryData.FeedbackId ? telemetryData.FeedbackId : '';
telemetryDataProperties.dataverseEntity = telemetryData.dataverseEntity ? telemetryData.dataverseEntity : '';
telemetryDataProperties.responseStatus = telemetryData.responseStatus ? telemetryData.responseStatus : '';

telemetryDataProperties.tokenSize = telemetryData.tokenSize ? telemetryData.tokenSize : '';

if (telemetryData.error) {
telemetryDataProperties.eventName = telemetryData.eventName;
telemetry.sendTelemetryException(telemetryData.error, telemetryDataProperties, telemetryDataMeasurements);
Expand Down
2 changes: 2 additions & 0 deletions src/common/copilot/telemetry/telemetryConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ export const CopilotNotificationShown = 'CopilotNotificationShown';
export const CopilotNotificationDoNotShowChecked = 'CopilotNotificationDoNotShowChecked';
export const CopilotNotificationDoNotShowUnchecked = 'CopilotNotificationDoNotShowUnchecked';
export const CopilotNotAvailable = 'CopilotNotAvailable';
export const CopilotExplainCode = 'CopilotExplainCode';
export const CopilotExplainCodeSize = 'CopilotExplainCodeSize';

0 comments on commit 525a0d2

Please sign in to comment.