349 lines
12 KiB
TypeScript
Raw Normal View History

2023-05-05 21:27:40 +08:00
import * as vscode from 'vscode';
import * as fs from 'fs';
2023-11-23 13:02:02 +08:00
import * as os from 'os';
2023-05-05 21:27:40 +08:00
import { sendFileSelectMessage, sendCodeSelectMessage } from './util';
2023-11-29 23:34:15 +08:00
import { ExtensionContextHolder } from '../util/extensionContext';
import { TopicManager } from '../topic/topicManager';
2023-05-31 16:10:53 +08:00
import { TopicTreeDataProvider, TopicTreeItem } from '../panel/topicView';
import { FilePairManager } from '../util/diffFilePairs';
2023-05-31 16:10:53 +08:00
import { ApiKeyManager } from '../util/apiKey';
import { UiUtilWrapper } from '../util/uiUtil';
import { isValidApiKey } from '../handler/historyMessagesBase';
2023-05-31 16:10:53 +08:00
import { logger } from '../util/logger';
import path from 'path';
2023-11-29 23:34:15 +08:00
import { sendCommandListByDevChatRun, updateChatModels } from '../handler/workflowCommandHandler';
2023-08-30 17:03:22 +08:00
import DevChat from "../toolwrapper/devchat";
2023-11-23 13:02:02 +08:00
import { createEnvByConda, createEnvByMamba } from '../util/python_installer/app_install';
import { installRequirements } from '../util/python_installer/package_install';
2023-11-29 23:34:15 +08:00
2023-05-11 10:27:54 +08:00
function registerOpenChatPanelCommand(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('devchat.openChatPanel', async () => {
await vscode.commands.executeCommand('devchat-view.focus');
});
context.subscriptions.push(disposable);
2023-05-05 21:27:40 +08:00
}
2023-05-16 14:35:37 +08:00
async function ensureChatPanel(context: vscode.ExtensionContext): Promise<boolean> {
await vscode.commands.executeCommand('devchat-view.focus');
return true;
2023-05-05 21:27:40 +08:00
}
function registerAddContextCommand(context: vscode.ExtensionContext) {
const callback = async (uri: { fsPath: any; }) => {
if (!await ensureChatPanel(context)) {
return;
}
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, uri.fsPath);
};
2023-11-29 23:34:15 +08:00
context.subscriptions.push(vscode.commands.registerCommand('devchat.addContext', callback));
context.subscriptions.push(vscode.commands.registerCommand('devchat.addConext_chinese', callback));
2023-05-05 21:27:40 +08:00
}
function registerAskForCodeCommand(context: vscode.ExtensionContext) {
const callback = async () => {
const editor = vscode.window.activeTextEditor;
if (editor) {
if (!await ensureChatPanel(context)) {
return;
}
const selectedText = editor.document.getText(editor.selection);
await sendCodeSelectMessage(ExtensionContextHolder.provider?.view()!, editor.document.fileName, selectedText, editor.selection.start.line);
}
};
context.subscriptions.push(vscode.commands.registerCommand('devchat.askForCode', callback));
context.subscriptions.push(vscode.commands.registerCommand('devchat.askForCode_chinese', callback));
2023-05-05 21:27:40 +08:00
}
function registerAskForFileCommand(context: vscode.ExtensionContext) {
const callback = async () => {
const editor = vscode.window.activeTextEditor;
if (editor) {
if (!await ensureChatPanel(context)) {
return;
}
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, editor.document.fileName);
}
};
context.subscriptions.push(vscode.commands.registerCommand('devchat.askForFile', callback));
context.subscriptions.push(vscode.commands.registerCommand('devchat.askForFile_chinese', callback));
2023-05-05 21:27:40 +08:00
}
function regAccessKeyCommand(context: vscode.ExtensionContext, provider: string) {
2023-05-31 16:10:53 +08:00
context.subscriptions.push(
vscode.commands.registerCommand(`DevChat.AccessKey.${provider}`, async () => {
2023-09-23 09:12:03 +08:00
vscode.commands.executeCommand("devchat-view.focus");
2023-09-23 08:16:45 +08:00
const passwordInput: string | undefined = await vscode.window.showInputBox({
2023-05-31 16:10:53 +08:00
password: true,
2023-09-23 08:13:00 +08:00
title: `Set ${provider} Key`,
placeHolder: `Input your ${provider} key. (Leave blank to clear the stored key.)`
2023-09-23 08:16:45 +08:00
}) ?? undefined;
if (passwordInput === undefined) {
return;
}
2023-12-07 09:11:21 +08:00
if (provider === "DevChat" && passwordInput.trim() !== "") {
if (!passwordInput.trim().startsWith("DC.")) {
UiUtilWrapper.showErrorMessage("Your key is invalid! DevChat Access Key is: DC.xxxxx");
return;
}
}
if (passwordInput.trim() !== "" && !isValidApiKey(passwordInput)) {
2023-09-23 07:39:19 +08:00
UiUtilWrapper.showErrorMessage("Your key is invalid!");
return ;
}
await ApiKeyManager.writeApiKeySecret(passwordInput, provider);
2023-09-23 07:22:17 +08:00
2023-10-26 22:45:31 +08:00
// update default model
const accessKey = await ApiKeyManager.getApiKey();
if (!accessKey) {
const modelList = await ApiKeyManager.getValidModels();
if (modelList && modelList.length > 0) {
// update default llm model
await UiUtilWrapper.updateConfiguration('devchat', 'defaultModel', modelList[0]);
}
}
// reload webview
ExtensionContextHolder.provider?.reloadWebview();
})
);
}
export function registerAccessKeySettingCommand(context: vscode.ExtensionContext) {
regAccessKeyCommand(context, "OpenAI");
regAccessKeyCommand(context, "Cohere");
regAccessKeyCommand(context, "Anthropic");
regAccessKeyCommand(context, "Replicate");
regAccessKeyCommand(context, "HuggingFace");
regAccessKeyCommand(context, "TogetherAI");
regAccessKeyCommand(context, "OpenRouter");
regAccessKeyCommand(context, "VertexAI");
regAccessKeyCommand(context, "AI21");
regAccessKeyCommand(context, "BaseTen");
regAccessKeyCommand(context, "Azure");
regAccessKeyCommand(context, "SageMaker");
regAccessKeyCommand(context, "Bedrock");
regAccessKeyCommand(context, "DevChat");
2023-05-31 16:10:53 +08:00
}
export function registerStatusBarItemClickCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devcaht.onStatusBarClick', async () => {
await vscode.commands.executeCommand('devchat-view.focus');
})
);
}
const topicDeleteCallback = async (item: TopicTreeItem) => {
const confirm = 'Delete';
const label = typeof item.label === 'string' ? item.label : item.label!.label;
const truncatedLabel = label.substring(0, 20) + (label.length > 20 ? '...' : '');
const result = await vscode.window.showWarningMessage(
`Are you sure you want to delete the topic "${truncatedLabel}"?`,
{ modal: true },
confirm
);
if (result === confirm) {
TopicManager.getInstance().deleteTopic(item.id);
}
};
2023-05-31 16:10:53 +08:00
export function regTopicDeleteCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat-topicview.deleteTopic', topicDeleteCallback)
2023-05-31 16:10:53 +08:00
);
}
export function regAddTopicCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat-topicview.addTopic', () => {
const topic = TopicManager.getInstance().createTopic();
TopicManager.getInstance().setCurrentTopic(topic.topicId);
})
);
}
export function regDeleteSelectTopicCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat-topicview.deleteSelectedTopic', () => {
const selectedItem = TopicTreeDataProvider.getInstance().selectedItem;
if (selectedItem) {
topicDeleteCallback(selectedItem);
2023-05-31 16:10:53 +08:00
} else {
vscode.window.showErrorMessage('No item selected');
}
})
);
}
export function regSelectTopicCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat-topicview.selectTopic', (item: TopicTreeItem) => {
TopicTreeDataProvider.getInstance().setSelectedItem(item);
TopicManager.getInstance().setCurrentTopic(item.id);
})
);
}
export function regReloadTopicCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat-topicview.reloadTopic', async () => {
2023-05-31 16:10:53 +08:00
TopicManager.getInstance().loadTopics();
})
);
}
export function regPythonPathCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat.PythonPath', async () => {
const pythonPath = await vscode.window.showInputBox({
title: "Set Python Path",
placeHolder: "Set Python Path"
}) ?? '';
if (pythonPath) {
2023-11-23 13:02:02 +08:00
vscode.workspace.getConfiguration("DevChat").update("PythonForChat", pythonPath, vscode.ConfigurationTarget.Global);
}
})
);
}
2023-05-31 16:10:53 +08:00
export function regApplyDiffResultCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat.applyDiffResult', async () => {
2023-05-31 16:10:53 +08:00
const activeEditor = vscode.window.activeTextEditor;
const fileName = activeEditor!.document.fileName;
const [leftUri, rightUri] = FilePairManager.getInstance().findPair(fileName) || [undefined, undefined];
if (leftUri && rightUri) {
// 获取对比的两个文件
const leftDoc = await vscode.workspace.openTextDocument(leftUri);
const rightDoc = await vscode.workspace.openTextDocument(rightUri);
2023-12-07 09:11:21 +08:00
// close rightDoc
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
2023-05-31 16:10:53 +08:00
// 将右边文档的内容替换到左边文档
const leftEditor = await vscode.window.showTextDocument(leftDoc);
await leftEditor.edit(editBuilder => {
const fullRange = new vscode.Range(0, 0, leftDoc.lineCount, 0);
editBuilder.replace(fullRange, rightDoc.getText());
});
// 保存左边文档
await leftDoc.save();
} else {
vscode.window.showErrorMessage('No file to apply diff result.');
}
})
);
}
export function registerInstallCommandsCommand(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('DevChat.InstallCommands', async () => {
2023-08-30 17:03:22 +08:00
const devchat = new DevChat();
await devchat.updateSysCommand();
2023-08-30 17:03:22 +08:00
sendCommandListByDevChatRun();
});
context.subscriptions.push(disposable);
}
export function registerUpdateChatModelsCommand(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('DevChat.UpdataChatModels', async () => {
updateChatModels();
});
context.subscriptions.push(disposable);
}
2023-11-23 13:02:02 +08:00
export function registerInstallCommandsPython(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('DevChat.InstallCommandPython', async () => {
// steps of install command python
// 1. install python >= 3.11
// 2. check requirements.txt in ~/.chat dir
// 3. install requirements.txt
2023-11-23 13:02:02 +08:00
// 1. install python >= 3.11
logger.channel()?.info(`create env for python ...`);
logger.channel()?.info(`try to create env by mamba ...`);
let pythonCommand = await createEnvByMamba("devchat-commands", "", "3.11.4");
if (!pythonCommand || pythonCommand === "") {
logger.channel()?.info(`create env by mamba failed, try to create env by conda ...`);
pythonCommand = await createEnvByConda("devchat-commands", "", "3.11.4");
}
2023-11-23 13:02:02 +08:00
if (!pythonCommand || pythonCommand === "") {
logger.channel()?.error(`create virtual python env failed, you need create it by yourself with command: "conda create -n devchat-commands python=3.11.4"`);
logger.channel()?.show();
2023-11-23 13:02:02 +08:00
return ;
}
2023-11-23 13:02:02 +08:00
// 2. check requirements.txt in ~/.chat dir
// ~/.chat/requirements.txt
const usrRequirementsFile = path.join(os.homedir(), '.chat', 'workflows', 'usr', 'requirements.txt');
const orgRequirementsFile = path.join(os.homedir(), '.chat', 'workflows', 'org', 'requirements.txt');
const sysRequirementsFile = path.join(os.homedir(), '.chat', 'workflows', 'sys', 'requirements.txt');
let requirementsFile = sysRequirementsFile;
if (fs.existsSync(orgRequirementsFile)) {
requirementsFile = orgRequirementsFile;
}
if (fs.existsSync(usrRequirementsFile)) {
requirementsFile = usrRequirementsFile;
}
2023-11-23 13:02:02 +08:00
if (!fs.existsSync(requirementsFile)) {
// logger.channel()?.warn(`requirements.txt not found in ~/.chat/workflows dir.`);
// logger.channel()?.show();
// vscode.window.showErrorMessage(`Error: see OUTPUT for more detail!`);
return ;
2023-11-23 13:02:02 +08:00
}
// 3. install requirements.txt
// run command: pip install -r {requirementsFile}
let isInstalled = false;
// try 3 times
for (let i = 0; i < 4; i++) {
let otherSource: string | undefined = undefined;
if (i>1) {
otherSource = 'https://pypi.tuna.tsinghua.edu.cn/simple/';
}
isInstalled = await installRequirements(pythonCommand, requirementsFile, otherSource);
if (isInstalled) {
break;
}
logger.channel()?.info(`Install packages failed, try again: ${i + 1}`);
}
if (!isInstalled) {
logger.channel()?.error(`Install packages failed, you can install it with command: "${pythonCommand} -m pip install -r ~/.chat/requirements.txt"`);
logger.channel()?.show();
vscode.window.showErrorMessage(`Error: see OUTPUT for more detail!`);
return '';
}
UiUtilWrapper.updateConfiguration("DevChat", "PythonForCommands", pythonCommand.trim());
vscode.window.showInformationMessage(`All slash Commands are ready to use! Please input / to try workflow commands!`);
2023-11-23 13:02:02 +08:00
});
2023-11-23 13:02:02 +08:00
context.subscriptions.push(disposable);
}
2023-05-05 21:27:40 +08:00
export {
registerOpenChatPanelCommand,
registerAddContextCommand,
registerAskForCodeCommand,
registerAskForFileCommand,
2023-05-05 21:27:40 +08:00
};