Explain spel expressions and queries with copilot

This commit is contained in:
vudayani
2024-08-14 18:33:54 +05:30
committed by Martin Lippert
parent 992a7c7428
commit 578ca3ec4e
15 changed files with 20412 additions and 9 deletions

View File

@@ -21,6 +21,7 @@ import {registerJavaDataService} from "@pivotal-tools/commons-vscode/lib/java-da
import * as setLogLevelUi from './set-log-levels-ui';
import { startTestJarSupport } from "./test-jar-launch";
import { startPropertiesConversionSupport } from "./convert-props-yaml";
import { activateCopilotFeatures } from "./copilot";
const PROPERTIES_LANGUAGE_ID = "spring-boot-properties";
const YAML_LANGUAGE_ID = "spring-boot-properties-yaml";
@@ -147,6 +148,8 @@ export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
registerClasspathService(client);
registerJavaDataService(client);
activateCopilotFeatures(context);
// Force classpath listener to be enabled. Boot LS can only be launched iff classpath is available and there Spring-Boot on the classpath somewhere.
commands.executeCommand('sts.vscode-spring-boot.enableClasspathListening', true);

View File

@@ -0,0 +1,111 @@
import { commands, ExtensionContext, extensions, lm, LanguageModelChatSelector, window, workspace, LogOutputChannel, version } from "vscode";
import { SemVer } from "semver";
export const REQUIRED_EXTENSION = 'github.copilot-chat';
const DEFAULT_MODEL_SELECTOR: LanguageModelChatSelector = { vendor: 'copilot', family: 'gpt-4' };
export const logger: LogOutputChannel = window.createOutputChannel("Spring tools copilot", { log: true });
export async function activateCopilotFeatures(context: ExtensionContext): Promise<void> {
if(!isLlmApiAvailable("1.90.0-insider")) { // lm API is available since 1.90.0-insider
return;
}
workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration('boot-java.highlight-copilot-codelens.on')) {
promptReloadWindow();
}
});
logger.info("vscode.lm is ready.");
await ensureExtensionInstalledAndActivated();
await updateConfigurationBasedOnCopilotAccess();
// Add listener to handle installation/uninstallation of the required extension
extensions.onDidChange(async () => {
await ensureExtensionInstalledAndActivated();
await updateConfigurationBasedOnCopilotAccess();
});
explainQueryWithCopilot();
}
function isLlmApiAvailable(v: string): boolean {
return new SemVer(version).compare(new SemVer(v)) >= 0;
}
async function ensureExtensionInstalledAndActivated() {
if (!isExtensionInstalled(REQUIRED_EXTENSION)) {
logger.error(`Required extension ${REQUIRED_EXTENSION} is not installed.`);
return;
}
if (!isExtensionActivated(REQUIRED_EXTENSION)) {
logger.error(`Required extension ${REQUIRED_EXTENSION} is not activated.`);
await waitUntilExtensionActivated(REQUIRED_EXTENSION);
}
}
function isExtensionInstalled(extensionId: string): boolean {
return !!extensions.getExtension(extensionId);
}
function isExtensionActivated(extensionId: string): boolean {
return !!extensions.getExtension(extensionId)?.isActive;
}
async function waitUntilExtensionActivated(extensionId: string, interval: number = 3500) {
logger.info(`Waiting for extension ${extensionId} to be activated...`);
return new Promise<void>((resolve) => {
const id = setInterval(() => {
if (extensions.getExtension(extensionId)?.isActive) {
clearInterval(id);
resolve();
}
}, interval);
});
}
async function updateConfigurationBasedOnCopilotAccess() {
if (!isExtensionInstalled(REQUIRED_EXTENSION) || !isExtensionActivated(REQUIRED_EXTENSION)) {
await updateConfiguration(false);
return;
}
const model = (await lm.selectChatModels(DEFAULT_MODEL_SELECTOR))?.[0];
if (!model) {
const models = await lm.selectChatModels();
logger.error(`No suitable model, available models: [${models.map(m => m.name).join(', ')}]. Please make sure you have installed the latest "GitHub Copilot Chat" (v0.16.0 or later) and all \`lm\` API is enabled.`);
await updateConfiguration(false);
} else {
await updateConfiguration(true);
}
}
async function updateConfiguration(value: boolean) {
const configValue = workspace.getConfiguration().get('boot-java.highlight-copilot-codelens.on');
if(value && configValue === true) {
commands.executeCommand('sts/enable/copilot/features', value);
}
}
async function explainQueryWithCopilot() {
commands.registerCommand('vscode-spring-boot.query.explain', async (userPrompt) => {
console.log('spel.explain: ' + userPrompt);
console.log('messages: ' + userPrompt);
await commands.executeCommand('workbench.action.chat.open', { query: userPrompt });
})
}
async function promptReloadWindow() {
const reload = await window.showInformationMessage(
'Configuration updated. Please reload VS Code to apply changes.',
'Reload'
);
if (reload === 'Reload') {
await commands.executeCommand('workbench.action.reloadWindow');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,9 @@
},
"categories": [
"Programming Languages",
"Linters"
"Linters",
"AI",
"Chat"
],
"keywords": [
"java-properties",
@@ -241,6 +243,11 @@
"enablement": "vscode-spring-boot.active-app-state == 'connected'",
"icon": "$(refresh)",
"category": "Spring Boot"
},
{
"command": "vscode-spring-boot.query.explain",
"title": "Explain Spel Expressions and Queries",
"category": "Spring Boot"
}
],
"configuration": [
@@ -379,6 +386,11 @@
],
"scope": "window",
"description": "Defines which browser to use when opening Spring Boot apps web pages."
},
"boot-java.highlight-copilot-codelens.on": {
"type": "boolean",
"default": false,
"description": "Explain SpEL Expressions and queries using Copilot"
}
}
},
@@ -1252,7 +1264,7 @@
},
"devDependencies": {
"@types/node": "^18.8.0",
"@types/vscode": "1.75.0",
"@types/semver": "^7.5.8",
"@vscode/vsce": "^2.22.0",
"typescript": "^4.8.0"
},