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

Chat ecl docs #431

Merged
merged 1 commit into from
Aug 23, 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ The following ECL specific commands are available. Note: These commands will *
| Submit (No Archive) | ctrl/cmd+F5 | Submit raw ECL without creating an archive |
| Verify ECL Signature | | Verify ECL Digital Signature |
| Language Reference Lookup | shift+F1 | For the currently selected text, search the online ECL language reference |
| Insert Record Definition | ctrl/cmd+I R | Fetches record definition for given logical file |
| Insert Record Definition | ctrl/cmd+R | Fetches record definition for given logical file |

#### Within the ECL Code Editor Tab Context Menu:

Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

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

23 changes: 21 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"@types/react": "17.0.80",
"@types/react-dom": "17.0.25",
"@types/tmp": "0.2.6",
"@types/vscode": "1.76.0",
"@types/vscode": "1.92.0",
"@types/vscode-notebook-renderer": "1.72.3",
"@typescript-eslint/eslint-plugin": "8.2.0",
"@typescript-eslint/parser": "8.2.0",
Expand Down Expand Up @@ -116,7 +116,7 @@
"url": "https://github.com/hpcc-systems/vscode-ecl.git"
},
"engines": {
"vscode": "^1.76.0"
"vscode": "^1.92.0"
},
"galleryBanner": {
"color": "#CFB69A",
Expand Down Expand Up @@ -205,6 +205,25 @@
"path": "./snippets/kel.json"
}
],
"chatParticipants": [
{
"id": "chat.ecl",
"fullName": "ECL",
"name": "ecl",
"description": "HPCC-Platform Assistant to help with ECL development",
"isSticky": true,
"commands": [
{
"name": "create",
"description": "Create a new ECL file"
},
{
"name": "docs",
"description": "Information about the ECL language"
}
]
}
],
"viewsWelcome": [],
"commands": [
{
Expand Down
53 changes: 53 additions & 0 deletions src/ecl/chat/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as vscode from "vscode";

export const ECL_COMMAND_ID = "ecl";
export const PROCESS_COPILOT_CREATE_CMD = "ecl.createFiles";
export const PROCESS_COPILOT_CREATE_CMD_TITLE = "Create ECL file";
export const COPILOT_CREATE_CMD = "ECL file";

export const OWNER = "hpcc-systems";
export const REPO = "HPCC-Platform";
export const BRANCH = "master";
export const SAMPLE_COLLECTION_URL = `https://cdn.jsdelivr.net/gh/${OWNER}/${REPO}@${BRANCH}/`;

export const MODEL_VENDOR: string = "copilot";

enum LANGUAGE_MODEL_ID {
GPT_3 = "gpt-3.5-turbo",
GPT_4 = "gpt-4",
GPT_4o = "gpt-4o"
}

export const MODEL_SELECTOR: vscode.LanguageModelChatSelector = { vendor: MODEL_VENDOR, family: LANGUAGE_MODEL_ID.GPT_4o };

export const FETCH_ISSUE_DETAIL_CMD = "Fetch Issue Details Command";

export enum commands {
DOCS = "docs",
ISSUES = "issues",
}

const GREETINGS = [
"Let me think how I can assist you... 🤔",
"Just a moment, I'm pondering... 💭",
"Give me a second, I'm working on it... ⏳",
"Hold on, let me figure this out... 🧐",
"One moment, I'm processing your request... ⏲️",
"Checking inside Gavins brain... 💭",
"Dans the man for this... 🧐",
"Working on your request... 🚀",
"Lets see what schmoo can do... 🕵️‍♂️",
"Let's see what we can do... 🕵️‍♂️",
"Let's get this sorted... 🗂️",
"Calling Jake for an answer... 💭",
"Hang tight, I'm on the case... 🕵️‍♀️",
"Analyzing the situation... 📊",
"Preparing the solution... 🛠️",
"Searching for the answer... 🔍",
"Maybe Mark knows... 🤔",
"Investigating the problem... 🕵️‍♂️"
];

export const getRandomGreeting = () => {
return GREETINGS[Math.floor(Math.random() * GREETINGS.length)];
};
91 changes: 91 additions & 0 deletions src/ecl/chat/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as vscode from "vscode";
import { commands, getRandomGreeting } from "./constants";
import localize from "../../util/localize";
import { handleDocsCommand } from "./prompts/docs";
import { handleIssueManagement } from "./prompts/issues";

const ECL_PARTICIPANT_ID = "chat.ecl";

interface IECLChatResult extends vscode.ChatResult {
metadata: {
command: string;
}
}

function handleError(logger: vscode.TelemetryLogger, err: any, stream: vscode.ChatResponseStream): void {
logger.logError(err);

if (err instanceof vscode.LanguageModelError) {
console.log(err.message, err.code, err.cause);
if (err.cause instanceof Error && err.cause.message.includes("off_topic")) {
stream.markdown(localize("I'm sorry, I can only explain ECL related topics."));
}
} else {
throw err;
}
}

let eclChat: ECLChat;

export class ECLChat {
protected constructor(ctx: vscode.ExtensionContext) {
const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, ctx: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise<IECLChatResult> => {
let cmdResult: any;
stream.progress(localize(getRandomGreeting()));
try {
if (request.command === commands.ISSUES) {
cmdResult = await handleIssueManagement(request, stream, token);
logger.logUsage("request", { kind: commands.ISSUES });
} else {
cmdResult = await handleDocsCommand(request, stream, token);
}
} catch (err) {
handleError(logger, err, stream);
}

return {
metadata: {
command: request.command || "",
},
};

};

const chatParticipant = vscode.chat.createChatParticipant(ECL_PARTICIPANT_ID, handler);
chatParticipant.iconPath = vscode.Uri.joinPath(ctx.extensionUri, "resources/hpcc-icon.png");

chatParticipant.followupProvider = {
provideFollowups(result: IECLChatResult, context: vscode.ChatContext, token: vscode.CancellationToken) {
return [];
}
};

const logger = vscode.env.createTelemetryLogger({
sendEventData(eventName, data) {
// Capture event telemetry
console.log(`Event: ${eventName}`);
console.log(`Data: ${JSON.stringify(data)}`);
},
sendErrorData(error, data) {
console.error(`Error: ${error}`);
console.error(`Data: ${JSON.stringify(data)}`);
}
});

ctx.subscriptions.push(chatParticipant.onDidReceiveFeedback((feedback: vscode.ChatResultFeedback) => {
logger.logUsage("chatResultFeedback", {
kind: feedback.kind
});
}));
}

static attach(ctx: vscode.ExtensionContext): ECLChat {
if (!eclChat) {
eclChat = new ECLChat(ctx);
}
return eclChat;
}
}

export function deactivate() { }

65 changes: 65 additions & 0 deletions src/ecl/chat/prompts/docs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as vscode from "vscode";
import { AssistantMessage, BasePromptElementProps, PromptElement, PromptSizing, TextChunk, UserMessage, } from "@vscode/prompt-tsx";
import { commands, MODEL_SELECTOR } from "../constants";
import { getChatResponse } from "../utils/index";
import { fetchContext, fetchIndexes, Hit, matchTopics } from "../../docs";
import * as prompts from "./templates/default";

export interface PromptProps extends BasePromptElementProps {
userQuery: string;
}

export interface DocsPromptProps extends PromptProps {
hits: Hit[]
}

export class DocsPrompt extends PromptElement<DocsPromptProps, any> {

render(state: void, sizing: PromptSizing) {
return (
<>
<AssistantMessage priority={1000}>{prompts.SYSTEM_MESSAGE}</AssistantMessage>
<UserMessage priority={500}>
{this.props.hits.map((hit, idx) => (
<TextChunk breakOn=' '>
{JSON.stringify(hit)}
</TextChunk>
))}
</UserMessage>
<UserMessage priority={1000}>{this.props.userQuery}</UserMessage>
</>
);
}
}

export async function handleDocsCommand(request: vscode.ChatRequest, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise<{ metadata: { command: string, hits: Hit[] } }> {
const [model] = await vscode.lm.selectChatModels(MODEL_SELECTOR);
if (model) {
const hits = await fetchContext(request.prompt);
let promptProps: DocsPromptProps;
if (!hits.length) {
promptProps = {
userQuery: `Suggest several (more 3 or more) web links that exist in the previous html content above that might help with the following question "${request.prompt}". The user can not see the above content. Explain why they might be helpful`,
hits: await fetchIndexes()
};
} else {
promptProps = {
userQuery: request.prompt,
hits,
};
}

const chatResponse = await getChatResponse(DocsPrompt, promptProps, token);
for await (const fragment of chatResponse.text) {
stream.markdown(fragment);
}

return {
metadata: {
command: commands.DOCS,
hits: promptProps.hits,
},
};
}
}

Loading