save work

This commit is contained in:
bobo.yang 2024-04-01 17:15:44 +08:00
parent 8cf7147b11
commit c52c4339e0
29 changed files with 22032 additions and 22960 deletions

View File

@ -1,5 +1,5 @@
module.exports = { module.exports = {
preset: "ts-jest", preset: "ts-jest",
testEnvironment: "node", testEnvironment: "node",
testMatch: ["<rootDir>/src/__test__/**/*.test.ts"], testMatch: ["<rootDir>/test/**/*.test.ts"],
}; };

43243
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,13 @@ import {
import { installRequirements } from "../util/python_installer/package_install"; import { installRequirements } from "../util/python_installer/package_install";
import { chatWithDevChat } from "../handler/chatHandler"; import { chatWithDevChat } from "../handler/chatHandler";
import { focusDevChatInput } from "../handler/focusHandler"; import { focusDevChatInput } from "../handler/focusHandler";
import { sendCommandListByDevChatRun } from '../handler/workflowCommandHandler';
import DevChat from "../toolwrapper/devchat";
import { createEnvByConda, createEnvByMamba } from '../util/python_installer/app_install';
import { installRequirements } from '../util/python_installer/package_install';
import { chatWithDevChat } from '../handler/chatHandler';
import { focusDevChatInput } from '../handler/focusHandler';
import { DevChatConfig } from '../util/config';
const readdir = util.promisify(fs.readdir); const readdir = util.promisify(fs.readdir);
const stat = util.promisify(fs.stat); const stat = util.promisify(fs.stat);
@ -46,7 +53,7 @@ async function copyDirectory(src: string, dest: string): Promise<void> {
} }
} }
function registerOpenChatPanelCommand(context: vscode.ExtensionContext) { export function registerOpenChatPanelCommand(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand( let disposable = vscode.commands.registerCommand(
"devchat.openChatPanel", "devchat.openChatPanel",
async () => { async () => {
@ -64,7 +71,7 @@ async function ensureChatPanel(
return true; return true;
} }
function registerAddContextCommand(context: vscode.ExtensionContext) { export function registerAddContextCommand(context: vscode.ExtensionContext) {
const callback = async (uri: { fsPath: any }) => { const callback = async (uri: { fsPath: any }) => {
if (!(await ensureChatPanel(context))) { if (!(await ensureChatPanel(context))) {
return; return;
@ -83,7 +90,7 @@ function registerAddContextCommand(context: vscode.ExtensionContext) {
); );
} }
function registerAskForCodeCommand(context: vscode.ExtensionContext) { export function registerAskForCodeCommand(context: vscode.ExtensionContext) {
const callback = async () => { const callback = async () => {
const editor = vscode.window.activeTextEditor; const editor = vscode.window.activeTextEditor;
if (editor) { if (editor) {
@ -108,7 +115,7 @@ function registerAskForCodeCommand(context: vscode.ExtensionContext) {
); );
} }
function registerAskForFileCommand(context: vscode.ExtensionContext) { export function registerAskForFileCommand(context: vscode.ExtensionContext) {
const callback = async () => { const callback = async () => {
const editor = vscode.window.activeTextEditor; const editor = vscode.window.activeTextEditor;
if (editor) { if (editor) {
@ -130,78 +137,6 @@ function registerAskForFileCommand(context: vscode.ExtensionContext) {
); );
} }
function regAccessKeyCommand(
context: vscode.ExtensionContext,
provider: string
) {
context.subscriptions.push(
vscode.commands.registerCommand(
`DevChat.AccessKey.${provider}`,
async () => {
vscode.commands.executeCommand("devchat-view.focus");
const passwordInput: string | undefined =
(await vscode.window.showInputBox({
password: true,
title: `Set ${provider} Key`,
placeHolder: `Input your ${provider} key. (Leave blank to clear the stored key.)`,
})) ?? undefined;
if (passwordInput === undefined) {
return;
}
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)) {
UiUtilWrapper.showErrorMessage("Your key is invalid!");
return;
}
await ApiKeyManager.writeApiKeySecret(passwordInput, provider);
// update default model
const defaultModel = await ApiKeyManager.llmModel();
if (!defaultModel) {
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");
}
export function registerStatusBarItemClickCommand( export function registerStatusBarItemClickCommand(
context: vscode.ExtensionContext context: vscode.ExtensionContext
@ -214,25 +149,18 @@ export function registerStatusBarItemClickCommand(
} }
export function regPythonPathCommand(context: vscode.ExtensionContext) { export function regPythonPathCommand(context: vscode.ExtensionContext) {
context.subscriptions.push( context.subscriptions.push(
vscode.commands.registerCommand("devchat.PythonPath", async () => { vscode.commands.registerCommand("devchat.PythonPath", async () => {
const pythonPath = const pythonPath = (await vscode.window.showInputBox({
(await vscode.window.showInputBox({ title: "Set Python Path",
title: "Set Python Path", placeHolder: "Set Python Path",
placeHolder: "Set Python Path", })) ?? "";
})) ?? "";
if (pythonPath) { if (pythonPath) {
vscode.workspace new DevChatConfig().set("python_for_chat", pythonPath);
.getConfiguration("DevChat") }
.update( })
"PythonForChat", );
pythonPath,
vscode.ConfigurationTarget.Global
);
}
})
);
} }
export function regApplyDiffResultCommand(context: vscode.ExtensionContext) { export function regApplyDiffResultCommand(context: vscode.ExtensionContext) {
@ -438,8 +366,79 @@ export function registerInstallCommandsPython(
// vscode.window.showInformationMessage(`All slash Commands are ready to use! Please input / to try workflow commands!`); // vscode.window.showInformationMessage(`All slash Commands are ready to use! Please input / to try workflow commands!`);
} }
); );
}
context.subscriptions.push(disposable); 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
// 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");
}
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();
return ;
}
// 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;
}
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 ;
}
// 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 '';
}
new DevChatConfig().set("python_for_commands", pythonCommand.trim());
// vscode.window.showInformationMessage(`All slash Commands are ready to use! Please input / to try workflow commands!`);
});
context.subscriptions.push(disposable);
} }
export function registerDevChatChatCommand(context: vscode.ExtensionContext) { export function registerDevChatChatCommand(context: vscode.ExtensionContext) {
@ -490,6 +489,7 @@ export function registerCodeLensRangeCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(disposable); context.subscriptions.push(disposable);
} }
export function registerHandleUri(context: vscode.ExtensionContext) { export function registerHandleUri(context: vscode.ExtensionContext) {
context.subscriptions.push( context.subscriptions.push(
vscode.window.registerUriHandler({ vscode.window.registerUriHandler({
@ -497,25 +497,7 @@ export function registerHandleUri(context: vscode.ExtensionContext) {
// 解析 URI 并执行相应的操作 // 解析 URI 并执行相应的操作
if (uri.path.includes("accesskey")) { if (uri.path.includes("accesskey")) {
const accessKey = uri.path.split("/")[2]; const accessKey = uri.path.split("/")[2];
const modelConfig: any = UiUtilWrapper.getConfiguration( new DevChatConfig().set("provides.devchat.api_key", accessKey);
"devchat",
"Provider.devchat"
);
const providerConfigNew: any = {};
if (Object.keys(modelConfig).length !== 0) {
for (const key of Object.keys(modelConfig || {})) {
const property = modelConfig![key];
providerConfigNew[key] = property;
}
}
providerConfigNew.access_key = accessKey;
vscode.workspace
.getConfiguration("devchat")
.update(
"Provider.devchat",
providerConfigNew,
vscode.ConfigurationTarget.Global
);
ensureChatPanel(context); ensureChatPanel(context);
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
setTimeout(() => { setTimeout(() => {
@ -529,7 +511,7 @@ export function registerHandleUri(context: vscode.ExtensionContext) {
); );
} }
function registerExplainCommand(context: vscode.ExtensionContext) { export function registerExplainCommand(context: vscode.ExtensionContext) {
const callback = async () => { const callback = async () => {
const editor = vscode.window.activeTextEditor; const editor = vscode.window.activeTextEditor;
if (editor) { if (editor) {
@ -548,7 +530,7 @@ function registerExplainCommand(context: vscode.ExtensionContext) {
); );
} }
function registerCommentCommand(context: vscode.ExtensionContext) { export function registerCommentCommand(context: vscode.ExtensionContext) {
const callback = async () => { const callback = async () => {
const editor = vscode.window.activeTextEditor; const editor = vscode.window.activeTextEditor;
if (editor) { if (editor) {
@ -567,7 +549,7 @@ function registerCommentCommand(context: vscode.ExtensionContext) {
); );
} }
function registerFixCommand(context: vscode.ExtensionContext) { export function registerFixCommand(context: vscode.ExtensionContext) {
const callback = async () => { const callback = async () => {
const editor = vscode.window.activeTextEditor; const editor = vscode.window.activeTextEditor;
if (editor) { if (editor) {
@ -585,13 +567,3 @@ function registerFixCommand(context: vscode.ExtensionContext) {
vscode.commands.registerCommand("devchat.fix_chinese", callback) vscode.commands.registerCommand("devchat.fix_chinese", callback)
); );
} }
export {
registerOpenChatPanelCommand,
registerAddContextCommand,
registerAskForCodeCommand,
registerAskForFileCommand,
registerExplainCommand,
registerFixCommand,
registerCommentCommand,
};

View File

@ -3,6 +3,7 @@
import { UiUtilWrapper } from "../util/uiUtil"; import { UiUtilWrapper } from "../util/uiUtil";
import { runCommand } from "../util/commonUtil"; import { runCommand } from "../util/commonUtil";
import { logger } from "../util/logger"; import { logger } from "../util/logger";
import { DevChatConfig } from "../util/config";
let devchatStatus = ''; let devchatStatus = '';
@ -42,14 +43,14 @@ function getDefaultPythonCommand(): string | undefined {
export function getValidPythonCommand(): string | undefined { export function getValidPythonCommand(): string | undefined {
try { try {
const pythonCommand = UiUtilWrapper.getConfiguration('DevChat', 'PythonForChat'); const pythonCommand = new DevChatConfig().get('python_for_chat');
if (pythonCommand) { if (pythonCommand) {
return pythonCommand; return pythonCommand;
} }
const defaultPythonCommand = getDefaultPythonCommand(); const defaultPythonCommand = getDefaultPythonCommand();
if (defaultPythonCommand) { if (defaultPythonCommand) {
UiUtilWrapper.updateConfiguration('DevChat', 'PythonForChat', defaultPythonCommand); new DevChatConfig().set('python_for_chat', defaultPythonCommand);
} }
return defaultPythonCommand; return defaultPythonCommand;

View File

@ -1,460 +1,136 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import { import {
registerOpenChatPanelCommand, registerOpenChatPanelCommand,
registerAddContextCommand, registerAddContextCommand,
registerAskForCodeCommand, registerAskForCodeCommand,
registerAskForFileCommand, registerAskForFileCommand,
registerAccessKeySettingCommand, regApplyDiffResultCommand,
regApplyDiffResultCommand, registerStatusBarItemClickCommand,
registerStatusBarItemClickCommand, regPythonPathCommand,
regPythonPathCommand, registerInstallCommandsCommand,
registerInstallCommandsCommand, registerInstallCommandsPython,
registerUpdateChatModelsCommand, registerDevChatChatCommand,
registerInstallCommandsPython, registerHandleUri,
registerDevChatChatCommand, registerCodeLensRangeCommand,
registerHandleUri, registerUpdateChatModelsCommand,
registerExplainCommand, registerCommentCommand,
registerCodeLensRangeCommand, registerFixCommand,
registerCommentCommand, registerExplainCommand,
registerFixCommand,
} from "./contributes/commands";
import { regLanguageContext } from "./contributes/context";
import { regDevChatView } from "./contributes/views";
import { ExtensionContextHolder } from "./util/extensionContext"; } from './contributes/commands';
import { logger } from "./util/logger"; import { regLanguageContext } from './contributes/context';
import { LoggerChannelVscode } from "./util/logger_vscode"; import { regDevChatView } from './contributes/views';
import { createStatusBarItem } from "./panel/statusBarView";
import { UiUtilWrapper } from "./util/uiUtil";
import { UiUtilVscode } from "./util/uiUtil_vscode";
import { ApiKeyManager } from "./util/apiKey";
import { startRpcServer } from "./ide_services/services";
import { registerCodeLensProvider } from "./panel/codeLens";
import { stopDevChatBase } from "./handler/sendMessageBase";
import exp from "constants";
/** import { ExtensionContextHolder } from './util/extensionContext';
* ABC isProviderHasSetted import { logger } from './util/logger';
* @returns import { LoggerChannelVscode } from './util/logger_vscode';
*/ import { createStatusBarItem } from './panel/statusBarView';
async function isProviderHasSetted() { import { UiUtilWrapper } from './util/uiUtil';
try { import { UiUtilVscode } from './util/uiUtil_vscode';
const providerProperty = "Provider.devchat"; import { startRpcServer } from './ide_services/services';
const providerConfig: any = UiUtilWrapper.getConfiguration( import { registerCodeLensProvider } from './panel/codeLens';
"devchat", import { stopDevChatBase } from './handler/sendMessageBase';
providerProperty import { DevChatConfig } from './util/config';
);
if (providerConfig && Object.keys(providerConfig).length > 0) {
return true;
}
const providerPropertyOpenAI = "Provider.openai";
const providerConfigOpenAI: any = UiUtilWrapper.getConfiguration(
"devchat",
providerPropertyOpenAI
);
if (providerConfigOpenAI && Object.keys(providerConfigOpenAI).length > 0) {
return true;
}
const apiOpenaiKey = await ApiKeyManager.getProviderApiKey("openai"); async function migrateConfig() {
if (apiOpenaiKey) { const devchatProvider = "providers.devchat";
return true; const devchatProviderConfig: any = new DevChatConfig().get(devchatProvider);
} if (devchatProviderConfig) {
const devchatKey = await ApiKeyManager.getProviderApiKey("devchat"); return ;
if (devchatKey) { }
return true;
}
return false; const devchatVScodeProvider: any = vscode.workspace.getConfiguration("devchat").get("Provider.devchat");
} catch (error) { if (devchatVScodeProvider && Object.keys(devchatVScodeProvider).length > 0) {
return false; if (devchatVScodeProvider["access_key"]) {
} new DevChatConfig().set("providers.devchat.api_key", devchatVScodeProvider["access_key"]);
} }
if (devchatVScodeProvider["api_base"]) {
new DevChatConfig().set("providers.devchat.api_base", devchatVScodeProvider["api_base"]);
}
}
const openaiVScodeProvider: any = vscode.workspace.getConfiguration("devchat").get("Provider.openai");
if (openaiVScodeProvider && Object.keys(openaiVScodeProvider).length > 0) {
if (openaiVScodeProvider["access_key"]) {
new DevChatConfig().set("providers.openai.api_key", openaiVScodeProvider["access_key"]);
}
if (openaiVScodeProvider["api_base"]) {
new DevChatConfig().set("providers.openai.api_base", openaiVScodeProvider["api_base"]);
}
}
async function configUpdateTo1115() { const devchatSecretKey = await UiUtilWrapper.secretStorageGet(`Access_KEY_DevChat`);
const supportModels = ["Model.gpt-3-5-1106", "Model.gpt-4-turbo"]; const openaiSecretKey = await UiUtilWrapper.secretStorageGet(`Access_KEY_OpenAI`);
for (const model of supportModels) { if (devchatSecretKey) {
const modelConfig1: any = UiUtilWrapper.getConfiguration("devchat", model); new DevChatConfig().set("providers.devchat.api_key", devchatSecretKey);
if (modelConfig1 && Object.keys(modelConfig1).length === 0) { }
let modelConfigNew = {}; if (openaiSecretKey) {
modelConfigNew = { provider: "devchat" }; new DevChatConfig().set("providers.openai.api_key", openaiSecretKey);
if (model.startsWith("Model.gpt-")) { }
modelConfigNew = { provider: "openai" };
}
try { const enableFunctionCalling = vscode.workspace.getConfiguration("DevChat").get("EnableFunctionCalling");
await vscode.workspace if (enableFunctionCalling) {
.getConfiguration("devchat") new DevChatConfig().set("enable_function_calling", enableFunctionCalling);
.update(model, modelConfigNew, vscode.ConfigurationTarget.Global); } else {
} catch (error) { new DevChatConfig().set("enable_function_calling", false);
logger.channel()?.error(`update Model.ERNIE-Bot error: ${error}`); }
}
}
}
}
async function configUpdateTo0924() { const betaInvitationCode = vscode.workspace.getConfiguration("DevChat").get("betaInvitationCode");
if (await isProviderHasSetted()) { if (betaInvitationCode) {
return; new DevChatConfig().set("beta_invitation_code", betaInvitationCode);
} } else {
const defaultModel: any = UiUtilWrapper.getConfiguration( new DevChatConfig().set("beta_invitation_code", "");
"devchat", }
"defaultModel"
);
let devchatKey = UiUtilWrapper.getConfiguration( const maxLogCount = vscode.workspace.getConfiguration("DevChat").get("maxLogCount");
"DevChat", if (maxLogCount) {
"Access_Key_DevChat" new DevChatConfig().set("max_log_count", maxLogCount);
); } else {
let openaiKey = UiUtilWrapper.getConfiguration("DevChat", "Api_Key_OpenAI"); new DevChatConfig().set("max_log_count", 20);
const endpointKey = UiUtilWrapper.getConfiguration("DevChat", "API_ENDPOINT"); }
devchatKey = undefined; const pythonForChat = vscode.workspace.getConfiguration("DevChat").get("PythonForChat");
openaiKey = undefined; if (pythonForChat) {
if (!devchatKey && !openaiKey) { new DevChatConfig().set("python_for_chat", pythonForChat);
openaiKey = await UiUtilWrapper.secretStorageGet("openai_OPENAI_API_KEY"); } else {
devchatKey = await UiUtilWrapper.secretStorageGet("devchat_OPENAI_API_KEY"); new DevChatConfig().set("python_for_chat", "");
await UiUtilWrapper.storeSecret("openai_OPENAI_API_KEY", ""); }
await UiUtilWrapper.storeSecret("devchat_OPENAI_API_KEY", "");
}
if (!devchatKey && !openaiKey) {
openaiKey = process.env.OPENAI_API_KEY;
}
let modelConfigNew = {}; const pythonForCommands = vscode.workspace.getConfiguration("DevChat").get("PythonForCommands");
let providerConfigNew = {}; if (pythonForCommands) {
if (openaiKey) { new DevChatConfig().set("python_for_commands", pythonForCommands);
providerConfigNew["access_key"] = openaiKey; } else {
if (endpointKey) { new DevChatConfig().set("python_for_commands", "");
providerConfigNew["api_base"] = endpointKey; }
}
await vscode.workspace const language = vscode.workspace.getConfiguration("DevChat").get("Language");
.getConfiguration("devchat") if (language) {
.update( new DevChatConfig().set("language", language);
"Provider.openai", } else {
providerConfigNew, new DevChatConfig().set("language", "en");
vscode.ConfigurationTarget.Global }
);
}
if (devchatKey) { const defaultModel = vscode.workspace.getConfiguration("devchat").get("defaultModel");
providerConfigNew["access_key"] = devchatKey; if (defaultModel) {
if (endpointKey) { new DevChatConfig().set("default_model", defaultModel);
providerConfigNew["api_base"] = endpointKey; } else {
} new DevChatConfig().set("default_model", "");
}
await vscode.workspace
.getConfiguration("devchat")
.update(
"Provider.devchat",
providerConfigNew,
vscode.ConfigurationTarget.Global
);
}
const supportModels = [
"Model.gpt-3-5",
"Model.gpt-3-5-1106",
"Model.gpt-3-5-16k",
"Model.gpt-4",
"Model.gpt-4-turbo",
"Model.xinghuo-2",
"Model.chatglm_pro",
"Model.ERNIE-Bot",
"Model.CodeLlama-34b-Instruct",
"Model.llama-2-70b-chat",
];
for (const model of supportModels) {
const modelConfig1: any = UiUtilWrapper.getConfiguration("devchat", model);
if (modelConfig1 && Object.keys(modelConfig1).length === 0) {
modelConfigNew = { provider: "devchat" };
if (model.startsWith("Model.gpt-")) {
modelConfigNew = { provider: "openai" };
}
await vscode.workspace
.getConfiguration("devchat")
.update(model, modelConfigNew, vscode.ConfigurationTarget.Global);
}
}
}
async function configUpdate0912To0924() {
if (await isProviderHasSetted()) {
return;
}
const oldModels = [
"Model.gpt-3-5",
"Model.gpt-3-5-16k",
"Model.gpt-4",
"Model.claude-2",
];
for (const model of oldModels) {
const modelConfig: any = UiUtilWrapper.getConfiguration("devchat", model);
if (modelConfig && Object.keys(modelConfig).length !== 0) {
let modelProperties: any = {};
for (const key of Object.keys(modelConfig || {})) {
const property = modelConfig![key];
modelProperties[key] = property;
}
if (modelConfig["api_key"]) {
let providerConfigNew = {};
providerConfigNew["access_key"] = modelConfig["api_key"];
if (modelConfig["api_base"]) {
providerConfigNew["api_base"] = modelConfig["api_base"];
}
if (modelConfig["api_key"].startsWith("DC.")) {
modelProperties["provider"] = "devchat";
await vscode.workspace
.getConfiguration("devchat")
.update(
"Provider.devchat",
providerConfigNew,
vscode.ConfigurationTarget.Global
);
} else {
modelProperties["provider"] = "openai";
await vscode.workspace
.getConfiguration("devchat")
.update(
"Provider.openai",
providerConfigNew,
vscode.ConfigurationTarget.Global
);
}
delete modelProperties["api_key"];
delete modelProperties["api_base"];
try {
await vscode.workspace
.getConfiguration("devchat")
.update(model, modelProperties, vscode.ConfigurationTarget.Global);
} catch (error) {
logger.channel()?.error(`error: ${error}`);
}
} else {
if (!modelProperties["provider"]) {
delete modelProperties["api_base"];
modelProperties["provider"] = "devchat";
try {
await vscode.workspace
.getConfiguration("devchat")
.update(
model,
modelProperties,
vscode.ConfigurationTarget.Global
);
} catch (error) {
logger.channel()?.error(`error: ${error}`);
}
}
}
}
}
}
async function configUpdateto240205() {
// rename Model.CodeLlama-34b-Instruct to Model.CodeLlama-70b
// add new Model.Mixtral-8x7B
// add new Model.Minimax-abab6
const supportModels = [
"Model.CodeLlama-70b",
"Model.Mixtral-8x7B",
"Model.Minimax-abab6",
];
for (const model of supportModels) {
const modelConfig1: any = UiUtilWrapper.getConfiguration("devchat", model);
if (modelConfig1 && Object.keys(modelConfig1).length === 0) {
let modelConfigNew = {};
modelConfigNew = { provider: "devchat" };
try {
await vscode.workspace
.getConfiguration("devchat")
.update(model, modelConfigNew, vscode.ConfigurationTarget.Global);
} catch (error) {
logger.channel()?.error(`error: ${error}`);
}
}
}
}
async function setLangDefaultValue() {
const lang = vscode.env.language;
if (!UiUtilWrapper.getConfiguration("DevChat", "Language")) {
if (lang.startsWith("zh-")) {
UiUtilWrapper.updateConfiguration("DevChat", "Language", "zh");
} else {
UiUtilWrapper.updateConfiguration("DevChat", "Language", "en");
}
}
}
async function updateInvalidSettings() {
const oldModels = [
"Model.gpt-3-5",
"Model.gpt-3-5-16k",
"Model.gpt-4",
"Model.claude-2",
];
for (const model of oldModels) {
const modelConfig: any = UiUtilWrapper.getConfiguration("devchat", model);
if (modelConfig && Object.keys(modelConfig).length !== 0) {
let modelProperties: any = {};
for (const key of Object.keys(modelConfig || {})) {
const property = modelConfig![key];
modelProperties[key] = property;
}
if (modelConfig["api_key"]) {
delete modelProperties["api_key"];
delete modelProperties["api_base"];
modelProperties["provider"] = "devchat";
try {
await vscode.workspace
.getConfiguration("devchat")
.update(model, modelProperties, vscode.ConfigurationTarget.Global);
} catch (error) {
logger.channel()?.error(`error: ${error}`);
}
}
}
}
}
async function updateInvalidDefaultModel() {
const defaultModel: any = UiUtilWrapper.getConfiguration(
"devchat",
"defaultModel"
);
if (
defaultModel === "gpt-3.5-turbo-1106" ||
defaultModel === "gpt-3.5-turbo-16k"
) {
try {
await vscode.workspace
.getConfiguration("devchat")
.update(
"defaultModel",
"gpt-3.5-turbo",
vscode.ConfigurationTarget.Global
);
} catch (error) {
logger.channel()?.error(`update Model.ERNIE-Bot error: ${error}`);
}
}
}
// "gpt-3.5-turbo-1106",
// "gpt-3.5-turbo-16k",
async function configSetModelDefaultParams() {
const modelParams = {
"Model.gpt-3-5": {
max_input_tokens: 13000,
},
"Model.gpt-4": {
max_input_tokens: 6000,
},
"Model.gpt-4-turbo": {
max_input_tokens: 32000,
},
"Model.claude-3-opus": {
max_input_tokens: 32000,
},
"Model.claude-3-sonnet": {
max_input_tokens: 32000,
},
"Model.xinghuo-2": {
max_input_tokens: 6000,
},
"Model.chatglm_pro": {
max_input_tokens: 8000,
},
"Model.ERNIE-Bot": {
max_input_tokens: 8000,
},
"Model.CodeLlama-70b": {
max_input_tokens: 4000,
},
"Model.Mixtral-8x7B": {
max_input_tokens: 4000,
},
"Model.Minimax-abab6": {
max_input_tokens: 4000,
},
"Model.llama-2-70b-chat": {
max_input_tokens: 4000,
},
};
// set default params
for (const model of Object.keys(modelParams)) {
const modelConfig: any = UiUtilWrapper.getConfiguration("devchat", model);
if (!modelConfig["max_input_tokens"]) {
modelConfig["max_input_tokens"] = modelParams[model]["max_input_tokens"];
try {
await vscode.workspace
.getConfiguration("devchat")
.update(model, modelConfig, vscode.ConfigurationTarget.Global);
} catch (error) {
logger.channel()?.error(`update Model.ERNIE-Bot error: ${error}`);
}
}
}
}
async function updateClaudePrivider() {
const claudeModels = ["Model.claude-3-opus", "Model.claude-3-sonnet"];
for (const model of claudeModels) {
const modelConfig: any = UiUtilWrapper.getConfiguration("devchat", model);
if (modelConfig && Object.keys(modelConfig).length === 0) {
const modelProperties: any = {
provider: "devchat",
};
try {
await vscode.workspace
.getConfiguration("devchat")
.update(model, modelProperties, vscode.ConfigurationTarget.Global);
} catch (error) {
logger.channel()?.error(`update ${model} error: ${error}`);
}
}
}
} }
async function activate(context: vscode.ExtensionContext) { async function activate(context: vscode.ExtensionContext) {
ExtensionContextHolder.context = context; ExtensionContextHolder.context = context;
logger.init(LoggerChannelVscode.getInstance()); logger.init(LoggerChannelVscode.getInstance());
UiUtilWrapper.init(new UiUtilVscode()); UiUtilWrapper.init(new UiUtilVscode());
await configUpdateTo0924(); await migrateConfig();
await configUpdate0912To0924();
await configUpdateTo1115();
await setLangDefaultValue();
await updateInvalidSettings();
await updateInvalidDefaultModel();
await configUpdateto240205();
await updateClaudePrivider();
await configSetModelDefaultParams();
regLanguageContext(); regLanguageContext();
regDevChatView(context);
regDevChatView(context);
registerAccessKeySettingCommand(context);
registerOpenChatPanelCommand(context); registerOpenChatPanelCommand(context);
registerAddContextCommand(context); registerAddContextCommand(context);
registerAskForCodeCommand(context); registerAskForCodeCommand(context);

View File

@ -1,35 +0,0 @@
/*
Update config
*/
import * as vscode from 'vscode';
import { regInMessage, regOutMessage } from '../util/reg_messages';
import { UiUtilWrapper } from '../util/uiUtil';
import { MessageHandler } from './messageHandler';
import { ApiKeyManager } from '../util/apiKey';
regInMessage({command: 'getUserAccessKey'});
regOutMessage({command: 'getUserAccessKey', accessKey: "DC.xxx", keyType: "DevChat", endPoint: "https://xxx"});
export async function getUserAccessKey(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
const workspaceDir = UiUtilWrapper.workspaceFoldersFirstPath();
const llmModelData = await ApiKeyManager.llmModel();
if (!llmModelData || !llmModelData.api_key) {
MessageHandler.sendMessage(panel,
{
"command": "getUserAccessKey",
"accessKey": "",
"keyType": "",
"endPoint": ""
}
);
return;
}
const keyData = {
"command": "getUserAccessKey",
"accessKey": llmModelData.api_key,
"keyType": llmModelData.api_key?.startsWith("DC.") ? "DevChat" : "others",
"endPoint": llmModelData.api_base ? llmModelData.api_base : ""
};
MessageHandler.sendMessage(panel, keyData, false);
}

View File

@ -0,0 +1,29 @@
/*
Commands for handling configuration read and write
*/
import * as vscode from 'vscode';
import { regInMessage, regOutMessage } from '../util/reg_messages';
import { MessageHandler } from './messageHandler';
import { DevChatConfig } from '../util/config';
regInMessage({command: 'readConfig', key: ''}); // when key is "", it will get all config values
regOutMessage({command: 'readConfig', key: '', value: 'any'});
export async function readConfig(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
if (message.key === '*' || message.key === '') {
const config = new DevChatConfig().getAll();
MessageHandler.sendMessage(panel, {command: 'readConfig', key: message.key, value: config});
} else {
const config = new DevChatConfig().get(message.key);
MessageHandler.sendMessage(panel, {command: 'readConfig', key: message.key, value: config});
}
}
regInMessage({command: 'writeConfig', key: '', value: 'any'}); // when key is "", it will rewrite all config values
export async function writeConfig(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
if (message.key === '*' || message.key === '') {
new DevChatConfig().setAll(message.value);
} else {
new DevChatConfig().set(message.key, message.value);
}
}

View File

@ -11,12 +11,11 @@ import { addConext } from './contextHandler';
import { getContextDetail } from './contextHandler'; import { getContextDetail } from './contextHandler';
import { listAllMessages } from './listMessages'; import { listAllMessages } from './listMessages';
import { doVscodeCommand } from './vscodeCommandHandler'; import { doVscodeCommand } from './vscodeCommandHandler';
import { getSetting, updateSetting } from './userSettingHandler';
import { featureToggle, getFeatureToggles } from './featureToggleHandler'; import { featureToggle, getFeatureToggles } from './featureToggleHandler';
import { getUserAccessKey } from './accessKeyHandler';
import { getValidLlmModelList } from './llmModelHandler';
import { readFile, writeFile } from './fileHandler'; import { readFile, writeFile } from './fileHandler';
import { getTopics, deleteTopic } from './topicHandler'; import { getTopics, deleteTopic } from './topicHandler';
import { readConfig, writeConfig } from './configHandler';
import { getSetting, getUserAccessKey, getValidLlmModelList, updateSetting } from './removehandler';
// According to the context menu selected by the user, add the corresponding context file // According to the context menu selected by the user, add the corresponding context file
@ -70,15 +69,9 @@ messageHandler.registerHandler('deleteChatMessage', deleteChatMessage);
// Response: none // Response: none
messageHandler.registerHandler('doCommand', doVscodeCommand); messageHandler.registerHandler('doCommand', doVscodeCommand);
messageHandler.registerHandler('updateSetting', updateSetting);
messageHandler.registerHandler('getSetting', getSetting);
messageHandler.registerHandler('featureToggle', featureToggle); messageHandler.registerHandler('featureToggle', featureToggle);
messageHandler.registerHandler('featureToggles', getFeatureToggles); messageHandler.registerHandler('featureToggles', getFeatureToggles);
messageHandler.registerHandler('getUserAccessKey', getUserAccessKey);
messageHandler.registerHandler('regModelList', getValidLlmModelList);
messageHandler.registerHandler('userInput', userInput); messageHandler.registerHandler('userInput', userInput);
messageHandler.registerHandler('readFile', readFile); messageHandler.registerHandler('readFile', readFile);
@ -87,3 +80,10 @@ messageHandler.registerHandler('writeFile', writeFile);
messageHandler.registerHandler('getTopics', getTopics); messageHandler.registerHandler('getTopics', getTopics);
messageHandler.registerHandler('deleteTopic', deleteTopic); messageHandler.registerHandler('deleteTopic', deleteTopic);
messageHandler.registerHandler('readConfig', readConfig);
messageHandler.registerHandler('writeConfig', writeConfig);
messageHandler.registerHandler('regModelList', getValidLlmModelList);
messageHandler.registerHandler('updateSetting', updateSetting);
messageHandler.registerHandler('getSetting', getSetting);
messageHandler.registerHandler('getUserAccessKey', getUserAccessKey);

View File

@ -137,9 +137,3 @@ export async function historyMessagesBase(topicId: string): Promise<LoadHistoryM
entries: logEntriesFlat.length > 0 ? logEntriesFlat : [], entries: logEntriesFlat.length > 0 ? logEntriesFlat : [],
} as LoadHistoryMessages; } as LoadHistoryMessages;
} }
export async function onApiKeyBase(apiKey: string): Promise<{ command: string, text: string, hash: string, user: string, date: string, isError: boolean }> {
return {
command: 'receiveMessage', text: `You need config access key for specified llmodel in setting view.`, hash: '', user: 'system', date: '', isError: false
};
}

View File

@ -1,8 +1,9 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { MessageHandler } from './messageHandler'; import { MessageHandler } from './messageHandler';
import { regInMessage, regOutMessage } from '../util/reg_messages'; import { regInMessage, regOutMessage } from '../util/reg_messages';
import { historyMessagesBase, LoadHistoryMessages, loadTopicHistoryFromCurrentMessageHistory, onApiKeyBase } from './historyMessagesBase'; import { historyMessagesBase, LoadHistoryMessages, loadTopicHistoryFromCurrentMessageHistory } from './historyMessagesBase';
import { UiUtilWrapper } from '../util/uiUtil'; import { UiUtilWrapper } from '../util/uiUtil';
import { DevChatConfig } from '../util/config';
@ -10,7 +11,7 @@ regInMessage({command: 'historyMessages', topicId: '', page: 0});
regOutMessage({command: 'loadHistoryMessages', entries: [{hash: '',user: '',date: '',request: '',response: '',context: [{content: '',role: ''}]}]}); regOutMessage({command: 'loadHistoryMessages', entries: [{hash: '',user: '',date: '',request: '',response: '',context: [{content: '',role: ''}]}]});
export async function getHistoryMessages(message: {command: string, topicId: string, page: number}, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> { export async function getHistoryMessages(message: {command: string, topicId: string, page: number}, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
// if history message has load, send it to webview // if history message has load, send it to webview
const maxCount = Number(UiUtilWrapper.getConfiguration('DevChat', 'maxLogCount')); const maxCount = Number(new DevChatConfig().get('max_log_count'));
const skip = maxCount * (message.page ? message.page : 0); const skip = maxCount * (message.page ? message.page : 0);
const topicId = message.topicId; const topicId = message.topicId;

View File

@ -1,16 +0,0 @@
import * as vscode from 'vscode';
import { MessageHandler } from './messageHandler';
import { regInMessage, regOutMessage } from '../util/reg_messages';
import { ApiKeyManager } from '../util/apiKey';
regInMessage({command: 'regModelList'});
regOutMessage({command: 'regModelList', result: [{name: ''}]});
export async function getValidLlmModelList(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
const modelList = await ApiKeyManager.getValidModels();
MessageHandler.sendMessage(panel, { command: 'regModelList', result: modelList });
return;
}

View File

@ -19,34 +19,40 @@ export class MessageHandler {
async handleMessage(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> { async handleMessage(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
let isNeedSendResponse = false; try {
if (message.command === 'sendMessage') { let isNeedSendResponse = false;
try { if (message.command === 'sendMessage') {
const messageText = message.text; try {
const messageObject = JSON.parse(messageText); const messageText = message.text;
if (messageObject && messageObject.user && messageObject.user === 'merico-devchat') { const messageObject = JSON.parse(messageText);
message = messageObject; if (messageObject && messageObject.user && messageObject.user === 'merico-devchat') {
isNeedSendResponse = true; message = messageObject;
if (messageObject.hasResponse) { isNeedSendResponse = true;
isNeedSendResponse = false; if (messageObject.hasResponse) {
isNeedSendResponse = false;
}
} }
} catch (e) {
} }
} catch (e) {
} }
}
const handler = this.handlers[message.command]; const handler = this.handlers[message.command];
if (handler) { if (handler) {
logger.channel()?.info(`Handling the command "${message.command}"`); logger.channel()?.info(`Handling the command "${message.command}"`);
await handler(message, panel); await handler(message, panel);
logger.channel()?.info(`Handling the command "${message.command}" done`); logger.channel()?.info(`Handling the command "${message.command}" done`);
} else { } else {
logger.channel()?.error(`No handler found for the command "${message.command}"`); logger.channel()?.error(`No handler found for the command "${message.command}"`);
logger.channel()?.show();
}
if (isNeedSendResponse) {
MessageHandler.sendMessage(panel, { command: 'receiveMessage', text: 'finish', hash: '', user: '', date: 1, isError: false });
}
} catch (e) {
logger.channel()?.error(`Error handling the message: "${JSON.stringify(message)}"`);
logger.channel()?.show(); logger.channel()?.show();
}
if (isNeedSendResponse) {
MessageHandler.sendMessage(panel, { command: 'receiveMessage', text: 'finish', hash: '', user: '', date: 1, isError: false });
} }
} }

View File

@ -0,0 +1,49 @@
import * as vscode from 'vscode';
import { MessageHandler } from './messageHandler';
import { regInMessage, regOutMessage } from '../util/reg_messages';
import { ApiKeyManager } from '../util/apiKey';
import { UiUtilWrapper } from '../util/uiUtil';
regInMessage({command: 'regModelList'});
regOutMessage({command: 'regModelList', result: [{name: ''}]});
export async function getValidLlmModelList(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
const modelList = ["model1", "model2", "model3"];
MessageHandler.sendMessage(panel, { command: 'regModelList', result: modelList });
return;
}
regInMessage({command: 'updateSetting', key1: "DevChat", key2: "OpenAI", value:"xxxx"});
export async function updateSetting(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
return ;
}
regInMessage({command: 'getSetting', key1: "DevChat", key2: "OpenAI"});
regOutMessage({command: 'getSetting', key1: "DevChat", key2: "OpenAI", value: "GPT-4"});
export async function getSetting(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
if (message.key2 === "Language") {
MessageHandler.sendMessage(panel, {"command": "getSetting", "key1": message.key1, "key2": message.key2, "value": "en"});
return;
}
MessageHandler.sendMessage(panel, {"command": "getSetting", "key1": message.key1, "key2": message.key2, "value": "model2"});
}
regInMessage({command: 'getUserAccessKey'});
regOutMessage({command: 'getUserAccessKey', accessKey: "DC.xxx", keyType: "DevChat", endPoint: "https://xxx"});
export async function getUserAccessKey(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
MessageHandler.sendMessage(panel,
{
"command": "getUserAccessKey",
"accessKey": "",
"keyType": "",
"endPoint": ""
}
);
return;
}

View File

@ -1,19 +0,0 @@
/*
Update config
*/
import * as vscode from 'vscode';
import { regInMessage, regOutMessage } from '../util/reg_messages';
import { UiUtilWrapper } from '../util/uiUtil';
import { MessageHandler } from './messageHandler';
regInMessage({command: 'updateSetting', key1: "DevChat", key2: "OpenAI", value:"xxxx"});
export async function updateSetting(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
UiUtilWrapper.updateConfiguration(message.key1, message.key2, message.value);
}
regInMessage({command: 'getSetting', key1: "DevChat", key2: "OpenAI"});
regOutMessage({command: 'getSetting', key1: "DevChat", key2: "OpenAI", value: "GPT-4"});
export async function getSetting(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
MessageHandler.sendMessage(panel, {"command": "getSetting", "key1": message.key1, "key2": message.key2, "value": UiUtilWrapper.getConfiguration(message.key1, message.key2)});
}

View File

@ -61,8 +61,3 @@ export async function sendCommandListByDevChatRun() {
await getWorkflowCommandList({}, existPannel!); await getWorkflowCommandList({}, existPannel!);
} }
} }
export async function updateChatModels() {
const modelList = await ApiKeyManager.getValidModels();
MessageHandler.sendMessage(existPannel!, { command: 'regModelList', result: modelList });
}

View File

@ -1,7 +1,7 @@
import { UiUtilWrapper } from "../../util/uiUtil"; import { DevChatConfig } from "../../util/config";
export async function ideLanguage() { export async function ideLanguage() {
const language = UiUtilWrapper.getConfiguration("DevChat", "Language"); const language = new DevChatConfig().get('language');
// 'en' stands for English, 'zh' stands for Simplified Chinese // 'en' stands for English, 'zh' stands for Simplified Chinese
return language; return language;
} }

View File

@ -2,9 +2,8 @@ import * as vscode from 'vscode';
import { dependencyCheck } from './statusBarViewBase'; import { dependencyCheck } from './statusBarViewBase';
import { ProgressBar } from '../util/progressBar'; import { ProgressBar } from '../util/progressBar';
import { ExtensionContextHolder } from '../util/extensionContext';
import { UiUtilWrapper } from '../util/uiUtil';
import { logger } from '../util/logger'; import { logger } from '../util/logger';
import { DevChatConfig } from '../util/config';
export function createStatusBarItem(context: vscode.ExtensionContext): vscode.StatusBarItem { export function createStatusBarItem(context: vscode.ExtensionContext): vscode.StatusBarItem {
@ -26,7 +25,7 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St
function checkDevChatCommandsStatus() { function checkDevChatCommandsStatus() {
const timerDevchatCommands = setInterval(async () => { const timerDevchatCommands = setInterval(async () => {
try { try {
const pythonCommand = UiUtilWrapper.getConfiguration("DevChat", "PythonForCommands"); const pythonCommand = new DevChatConfig().get('python_for_commands');
if (!pythonCommand) { if (!pythonCommand) {
statusBarItem.text = `$(pass)DevChat$(warning)`; statusBarItem.text = `$(pass)DevChat$(warning)`;
statusBarItem.tooltip = `ready to chat, command functionality limited`; statusBarItem.tooltip = `ready to chat, command functionality limited`;
@ -48,7 +47,7 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St
try { try {
progressBar.update("Checking dependencies", 0); progressBar.update("Checking dependencies", 0);
const [devchatStatus, apiKeyStatus] = await dependencyCheck(); const devchatStatus = await dependencyCheck();
if (devchatStatus !== 'has statisfied the dependency' && devchatStatus !== 'DevChat has been installed') { if (devchatStatus !== 'has statisfied the dependency' && devchatStatus !== 'DevChat has been installed') {
statusBarItem.text = `$(warning)DevChat`; statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `${devchatStatus}`; statusBarItem.tooltip = `${devchatStatus}`;
@ -64,14 +63,6 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St
return; return;
} }
if (apiKeyStatus !== 'has valid access key') {
statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `${apiKeyStatus}`;
statusBarItem.command = 'DevChat.AccessKey.DevChat';
progressBar.update(`Checking dependencies: ${apiKeyStatus}.`, 0);
return;
}
statusBarItem.text = `$(pass)DevChat`; statusBarItem.text = `$(pass)DevChat`;
statusBarItem.tooltip = `ready to chat`; statusBarItem.tooltip = `ready to chat`;
statusBarItem.command = 'devcaht.onStatusBarClick'; statusBarItem.command = 'devcaht.onStatusBarClick';

View File

@ -7,22 +7,12 @@ import { ApiKeyManager } from '../util/apiKey';
import { installDevchat } from '../util/python_installer/install_devchat'; import { installDevchat } from '../util/python_installer/install_devchat';
function getExtensionVersion(): string {
const packageJsonPath = path.join(UiUtilWrapper.extensionPath(), 'package.json');
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
const packageJson = JSON.parse(packageJsonContent);
return packageJson.version;
}
let devchatStatus = ''; let devchatStatus = '';
let apiKeyStatus = ''; let apiKeyStatus = '';
let preDevchatStatus = ''; let preDevchatStatus = '';
let preApiKeyStatus = '';
export async function dependencyCheck(): Promise<[string, string]> { export async function dependencyCheck(): Promise<string> {
// there are some different status of devchat: // there are some different status of devchat:
// 0. not checked // 0. not checked
// 1. has statisfied the dependency // 1. has statisfied the dependency
@ -62,33 +52,12 @@ export async function dependencyCheck(): Promise<[string, string]> {
return ""; return "";
}; };
// define subfunction to check api key
const getApiKeyStatus = async (): Promise<string> => {
if (apiKeyStatus === '' || apiKeyStatus === 'Click "DevChat" status icon to set key') {
const defaultModel = await ApiKeyManager.llmModel();
if (defaultModel) {
apiKeyStatus = 'has valid access key';
return apiKeyStatus;
} else {
apiKeyStatus = 'Click "DevChat" status icon to set key';
return apiKeyStatus;
}
} else {
return apiKeyStatus;
}
};
const devchatPackageStatus = await getDevChatStatus(); const devchatPackageStatus = await getDevChatStatus();
const apiAccessKeyStatus = await getApiKeyStatus();
if (devchatPackageStatus !== preDevchatStatus) { if (devchatPackageStatus !== preDevchatStatus) {
logger.channel()?.info(`devchat status: ${devchatPackageStatus}`); logger.channel()?.info(`devchat status: ${devchatPackageStatus}`);
preDevchatStatus = devchatPackageStatus; preDevchatStatus = devchatPackageStatus;
} }
if (apiAccessKeyStatus !== preApiKeyStatus) {
logger.channel()?.info(`api key status: ${apiAccessKeyStatus}`);
preApiKeyStatus = apiAccessKeyStatus;
}
return [devchatPackageStatus, apiAccessKeyStatus]; return devchatPackageStatus;
} }

View File

@ -4,12 +4,13 @@ import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import { logger } from '../util/logger'; import { logger } from '../util/logger';
import { CommandRun, saveModelSettings } from "../util/commonUtil"; import { CommandRun } from "../util/commonUtil";
import { UiUtilWrapper } from '../util/uiUtil'; import { UiUtilWrapper } from '../util/uiUtil';
import { ApiKeyManager } from '../util/apiKey'; import { ApiKeyManager } from '../util/apiKey';
import { assertValue } from '../util/check'; import { assertValue } from '../util/check';
import { getFileContent } from '../util/commonUtil'; import { getFileContent } from '../util/commonUtil';
import * as toml from '@iarna/toml'; import * as toml from '@iarna/toml';
import { DevChatConfig } from '../util/config';
const readFileAsync = fs.promises.readFile; const readFileAsync = fs.promises.readFile;
@ -122,7 +123,7 @@ class DevChat {
assertValue(!llmModelData || !llmModelData.model, 'You must select a LLM model to use for conversations'); assertValue(!llmModelData || !llmModelData.model, 'You must select a LLM model to use for conversations');
args.push("-m", llmModelData.model); args.push("-m", llmModelData.model);
const functionCalling = UiUtilWrapper.getConfiguration('DevChat', 'EnableFunctionCalling'); const functionCalling = new DevChatConfig().get('enable_function_calling');
if (functionCalling) { if (functionCalling) {
args.push("-a"); args.push("-a");
} }
@ -139,7 +140,7 @@ class DevChat {
if (options.maxCount) { if (options.maxCount) {
args.push('--max-count', `${options.maxCount}`); args.push('--max-count', `${options.maxCount}`);
} else { } else {
const maxLogCount = UiUtilWrapper.getConfiguration('DevChat', 'maxLogCount'); const maxLogCount = new DevChatConfig().get('max_log_count');
args.push('--max-count', `${maxLogCount}`); args.push('--max-count', `${maxLogCount}`);
} }
@ -210,7 +211,7 @@ class DevChat {
"PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages" "PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages"
}; };
const pythonApp = UiUtilWrapper.getConfiguration("DevChat", "PythonForChat") || "python3"; const pythonApp = new DevChatConfig().get('python_for_chat') || "python3";
// run command // run command
const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync( const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync(
@ -250,7 +251,7 @@ class DevChat {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
"PYTHONUTF8": 1, "PYTHONUTF8": 1,
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
"command_python": UiUtilWrapper.getConfiguration('DevChat', 'PythonForCommands') || "", "command_python": new DevChatConfig().get('python_for_commands') || "",
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
"PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages", "PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages",
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -266,9 +267,6 @@ class DevChat {
env: envs env: envs
}; };
// save llm model config
await saveModelSettings();
logger.channel()?.info(`api_key: ${llmModelData.api_key.replace(/^(.{4})(.*)(.{4})$/, (_, first, middle, last) => first + middle.replace(/./g, '*') + last)}`); logger.channel()?.info(`api_key: ${llmModelData.api_key.replace(/^(.{4})(.*)(.{4})$/, (_, first, middle, last) => first + middle.replace(/./g, '*') + last)}`);
logger.channel()?.info(`api_base: ${llmModelData.api_base}`); logger.channel()?.info(`api_base: ${llmModelData.api_base}`);
@ -281,7 +279,7 @@ class DevChat {
onData(data); onData(data);
}; };
// run command // run command
const pythonApp = UiUtilWrapper.getConfiguration("DevChat", "PythonForChat") || "python3"; const pythonApp = new DevChatConfig().get('python_for_chat') || "python3";
logger.channel()?.info(`Running devchat:${pythonApp} ${args.join(" ")}`); logger.channel()?.info(`Running devchat:${pythonApp} ${args.join(" ")}`);
const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync(pythonApp, args, spawnAsyncOptions, onStdoutPartial, undefined, undefined, undefined); const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync(pythonApp, args, spawnAsyncOptions, onStdoutPartial, undefined, undefined, undefined);
// handle result // handle result

View File

@ -1,215 +1,48 @@
// src/apiKey.ts // src/apiKey.ts
import DevChat from '@/toolwrapper/devchat';
import { UiUtilWrapper } from './uiUtil'; import { UiUtilWrapper } from './uiUtil';
import { DevChatConfig } from './config';
import { logger } from './logger';
export class ApiKeyManager { export class ApiKeyManager {
static toProviderKey(provider: string) : string | undefined {
let providerNameMap = {
"openai": "OpenAI",
"devchat": "DevChat"
};
return providerNameMap[provider];
}
static async getValidModels(): Promise<string[]> {
const modelProperties = async (modelPropertyName: string, modelName: string) => {
const modelConfig = UiUtilWrapper.getConfiguration("devchat", modelPropertyName);
if (!modelConfig) {
return undefined;
}
let modelProperties: any = {};
for (const key of Object.keys(modelConfig || {})) {
const property = modelConfig![key];
modelProperties[key] = property;
}
if (!modelConfig["provider"]) {
return undefined;
}
const apiKey = await this.getProviderApiKey(modelConfig["provider"]);
if (apiKey) {
modelProperties["api_key"] = apiKey;
} else {
const apiKeyDevChat = await this.getProviderApiKey("devchat");
if (apiKeyDevChat) {
modelProperties["api_key"] = apiKeyDevChat;
} else {
return undefined;
}
}
modelProperties['model'] = modelName;
return modelProperties;
};
let modelList : string[] = [];
const openaiModel = await modelProperties('Model.gpt-3-5', "gpt-3.5-turbo");
if (openaiModel) {
modelList.push(openaiModel.model);
}
const openaiModel3 = await modelProperties('Model.gpt-4', "gpt-4");
if (openaiModel3) {
modelList.push(openaiModel3.model);
}
const openaiModel4 = await modelProperties('Model.gpt-4-turbo', "gpt-4-turbo-preview");
if (openaiModel4) {
modelList.push(openaiModel4.model);
}
const claude3sonnetModel = await modelProperties('Model.claude-3-sonnet', "claude-3-sonnet");
if (claude3sonnetModel) {
modelList.push(claude3sonnetModel.model);
}
const claude3opusModel = await modelProperties('Model.claude-3-opus', "claude-3-opus");
if (claude3opusModel) {
modelList.push(claude3opusModel.model);
}
const xinghuoModel = await modelProperties('Model.xinghuo-2', "xinghuo-3.5");
if (xinghuoModel) {
modelList.push(xinghuoModel.model);
}
const glmModel = await modelProperties('Model.chatglm_pro', "GLM-4");
if (glmModel) {
modelList.push(glmModel.model);
}
const erniebotModel = await modelProperties('Model.ERNIE-Bot', "ERNIE-Bot-4.0");
if (erniebotModel) {
modelList.push(erniebotModel.model);
}
const llamaCode2Model = await modelProperties('Model.CodeLlama-70b', "togetherai/codellama/CodeLlama-70b-Instruct-hf");
if (llamaCode2Model) {
modelList.push(llamaCode2Model.model);
}
const mixtralCode2Model = await modelProperties('Model.Mixtral-8x7B', "togetherai/mistralai/Mixtral-8x7B-Instruct-v0.1");
if (mixtralCode2Model) {
modelList.push(mixtralCode2Model.model);
}
const minimaxCode2Model = await modelProperties('Model.Minimax-abab6', "minimax/abab6-chat");
if (minimaxCode2Model) {
modelList.push(minimaxCode2Model.model);
}
const llama70BModel = await modelProperties('Model.llama-2-70b-chat', "llama-2-70b-chat");
if (llama70BModel) {
modelList.push(llama70BModel.model);
}
return modelList;
}
static async llmModel() { static async llmModel() {
// inner function to update default model const defaultModel = new DevChatConfig().get('default_model');
const updateDefaultModelWithValidModels = async () => { if (!defaultModel) {
const validModels = await this.getValidModels(); return undefined;
if (validModels.length > 0) { }
await UiUtilWrapper.updateConfiguration('devchat', 'defaultModel', validModels[0]);
return validModels[0]; // get model provider
} else { const defaultModelProvider = new DevChatConfig().get(`models.${defaultModel}.provider`);
return undefined; if (!defaultModelProvider) {
} return undefined;
}; }
// inner function to get model properties // get provider config
const modelProperties = async (modelPropertyName: string, modelName: string) => { const defaultProvider = new DevChatConfig().get(`providers.${defaultModelProvider}`);
const modelConfig = UiUtilWrapper.getConfiguration("devchat", modelPropertyName); const devchatProvider = new DevChatConfig().get(`providers.devchat`);
if (!modelConfig) {
return undefined; let defaultModelConfig = new DevChatConfig().get(`models.${defaultModel}`);
} defaultModelConfig["model"] = defaultModel;
if (defaultProvider) {
let modelProperties: any = {}; for (const key of Object.keys(defaultProvider || {})) {
for (const key of Object.keys(modelConfig || {})) { const property = defaultProvider[key];
const property = modelConfig![key]; defaultModelConfig[key] = property;
modelProperties[key] = property; }
} return defaultModelConfig;
if (!modelConfig["provider"]) { } else if (devchatProvider) {
return undefined; for (const key of Object.keys(devchatProvider || {})) {
} const property = devchatProvider[key];
defaultModelConfig[key] = property;
const apiKey = await this.getProviderApiKey(modelConfig["provider"]); }
const apiBase = await this.getProviderApiBase(modelConfig["provider"]); if (!defaultModelConfig["api_base"]) {
logger.channel()?.error("api_base is not set in devchat provider!!!");
if (apiKey) { logger.channel()?.show();
modelProperties["api_key"] = apiKey; }
} else { return defaultModelConfig;
const apiKeyDevChat = await this.getProviderApiKey("devchat"); } else {
if (apiKeyDevChat) {
modelProperties["api_key"] = apiKeyDevChat;
} else {
return undefined;
}
}
if (apiBase) {
modelProperties["api_base"] = apiBase;
} else if (!apiKey) {
const devchatApiBase = await this.getProviderApiBase("devchat");
if (devchatApiBase) {
modelProperties["api_base"] = devchatApiBase;
}
}
if (!modelProperties["api_base"] && modelProperties["api_key"]?.startsWith("DC.")) {
modelProperties["api_base"] = "https://api.devchat.ai/v1";
}
modelProperties['model'] = modelName;
return modelProperties;
};
// inner function visit all models
const getModelPropertiesByName = async (modelName: string) => {
if (modelName === "gpt-3.5-turbo") {
return await modelProperties('Model.gpt-3-5', "gpt-3.5-turbo");
}
if (modelName === "gpt-4") {
return await modelProperties('Model.gpt-4', "gpt-4");
}
if (modelName === "gpt-4-turbo-preview") {
return await modelProperties('Model.gpt-4-turbo', "gpt-4-turbo-preview");
}
if (modelName === "claude-3-sonnet") {
return await modelProperties('Model.claude-3-sonnet', "claude-3-sonnet");
}
if (modelName === "claude-3-opus") {
return await modelProperties('Model.claude-3-opus', "claude-3-opus");
}
if (modelName === "xinghuo-3.5") {
return await modelProperties('Model.xinghuo-2', "xinghuo-3.5");
}
if (modelName === "GLM-4") {
return await modelProperties('Model.chatglm_pro', "GLM-4");
}
if (modelName === "ERNIE-Bot-4.0") {
return await modelProperties('Model.ERNIE-Bot', "ERNIE-Bot-4.0");
}
if (modelName === "togetherai/codellama/CodeLlama-70b-Instruct-hf") {
return await modelProperties('Model.CodeLlama-70b', "togetherai/codellama/CodeLlama-70b-Instruct-hf");
}
if (modelName === "togetherai/mistralai/Mixtral-8x7B-Instruct-v0.1") {
return await modelProperties('Model.Mixtral-8x7B', "togetherai/mistralai/Mixtral-8x7B-Instruct-v0.1");
}
if (modelName === "minimax/abab6-chat") {
return await modelProperties('Model.Minimax-abab6', "minimax/abab6-chat");
}
if (modelName === "llama-2-70b-chat") {
return await modelProperties('Model.llama-2-70b-chat', "llama-2-70b-chat");
}
return undefined;
};
let llmModelT: string | undefined = UiUtilWrapper.getConfiguration('devchat', 'defaultModel');
if (llmModelT) {
const defaultModel = await getModelPropertiesByName(llmModelT);
if (defaultModel) {
return defaultModel;
}
}
// reset default model
llmModelT = await updateDefaultModelWithValidModels();
if (!llmModelT) {
return undefined; return undefined;
} }
return getModelPropertiesByName(llmModelT);
} }
static getKeyType(apiKey: string): string | undefined { static getKeyType(apiKey: string): string | undefined {
@ -221,43 +54,4 @@ export class ApiKeyManager {
return undefined; return undefined;
} }
} }
static async writeApiKeySecret(apiKey: string, llmType: string = "Unknow"): Promise<void> {
await UiUtilWrapper.storeSecret(`Access_KEY_${llmType}`, apiKey);
}
static async loadApiKeySecret(llmType: string = "Unknow"): Promise<string | undefined> {
return await UiUtilWrapper.secretStorageGet(`Access_KEY_${llmType}`);
}
// get some provider's api key
static async getProviderApiKey(provider: string): Promise<string | undefined> {
// read key from configration first
const providerProperty = `Provider.${provider}`;
const providerConfig = UiUtilWrapper.getConfiguration("devchat", providerProperty);
if (providerConfig) {
if (providerConfig["access_key"]) {
return providerConfig["access_key"];
}
}
const providerName = this.toProviderKey(provider);
if (!providerName) {
return undefined;
}
return await this.loadApiKeySecret(providerName);
}
// get some provider's api base
static async getProviderApiBase(provider: string): Promise<string | undefined> {
// read key from configration first
const providerProperty = `Provider.${provider}`;
const providerConfig = UiUtilWrapper.getConfiguration("devchat", providerProperty);
if (providerConfig) {
if (providerConfig["api_base"]) {
return providerConfig["api_base"];
}
}
return undefined;
}
} }

View File

@ -14,54 +14,6 @@ import { UiUtilWrapper } from './uiUtil';
import { ApiKeyManager } from './apiKey'; import { ApiKeyManager } from './apiKey';
var kill = require('tree-kill'); var kill = require('tree-kill');
export async function saveModelSettings(): Promise<void> {
// support models
const supportModels = {
"Model.gpt-3-5": "gpt-3.5-turbo",
"Model.gpt-4": "gpt-4",
"Model.gpt-4-turbo": "gpt-4-turbo-preview",
"Model.claude-3-sonnet": "claude-3-sonnet",
"Model.claude-3-opus": "claude-3-opus",
"Model.xinghuo-2": "xinghuo-3.5",
"Model.chatglm_pro": "GLM-4",
"Model.ERNIE-Bot": "ERNIE-Bot-4.0",
"Model.CodeLlama-70b": "togetherai/codellama/CodeLlama-70b-Instruct-hf",
"Model.Mixtral-8x7B": "togetherai/mistralai/Mixtral-8x7B-Instruct-v0.1",
"Model.Minimax-abab6": "minimax/abab6-chat",
"Model.llama-2-70b-chat": "llama-2-70b-chat"
};
// is enable stream
const openaiStream = UiUtilWrapper.getConfiguration('DevChat', 'OpenAI.stream');
let devchatConfig = {};
for (const model of Object.keys(supportModels)) {
const modelConfig = UiUtilWrapper.getConfiguration('devchat', model);
if (modelConfig) {
devchatConfig[supportModels[model]] = {
"stream": openaiStream
};
for (const key of Object.keys(modelConfig || {})) {
const property = modelConfig![key];
devchatConfig[supportModels[model]][key] = property;
}
}
}
let devchatModels = {
// eslint-disable-next-line @typescript-eslint/naming-convention
"default_model": "gpt-3.5-turbo",
"models": devchatConfig
};
// write to config file
const os = process.platform;
const userHome = os === 'win32' ? fs.realpathSync(process.env.USERPROFILE || '') : process.env.HOME;
const configPath = path.join(userHome!, '.chat', 'config.yml');
// write devchatConfig to configPath
const yamlString = yaml.stringify(devchatModels);
fs.writeFileSync(configPath, yamlString);
}
async function createOpenAiKeyEnv() { async function createOpenAiKeyEnv() {
let envs = {...process.env}; let envs = {...process.env};

60
src/util/config.ts Normal file
View File

@ -0,0 +1,60 @@
import fs from 'fs';
import yaml from 'yaml';
import path from 'path';
import { logger } from './logger';
export class DevChatConfig {
private configFilePath: string;
private data: any;
constructor() {
// 视操作系统的差异,可能需要调整路径 ~/.chat/config.yml
this.configFilePath = path.join(process.env.HOME || process.env.USERPROFILE || '', '.chat', 'config.yml');
this.readConfigFile();
}
private readConfigFile() {
try {
const fileContents = fs.readFileSync(this.configFilePath, 'utf8');
this.data = yaml.parse(fileContents);
} catch (error) {
logger.channel()?.error(`Error reading the config file: ${error}`);
logger.channel()?.show();
this.data = {};
}
}
private writeConfigFile() {
try {
const yamlStr = yaml.stringify(this.data);
fs.writeFileSync(this.configFilePath, yamlStr, 'utf8');
} catch (error) {
logger.channel()?.error(`Error writing the config file: ${error}`);
logger.channel()?.show();
}
}
public get(key: string): any {
return key.split('.').reduce((prev, curr) => prev ? prev[curr] : undefined, this.data);
}
public set(key: string, value: any): void {
let keys = key.split('.');
let lastKey = keys.pop();
let lastObj = keys.reduce((prev, k) => prev[k] = prev[k] || {}, this.data); // 这创建一个嵌套的对象结构,如果不存在的话
if (lastKey) {
lastObj[lastKey] = value; // 设置值
}
this.writeConfigFile(); // 更新配置文件
}
public getAll(): any {
return this.data;
}
public setAll(newData: any): void {
this.data = newData;
this.writeConfigFile(); // 更新配置文件
}
}

View File

@ -1,6 +1,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { DevChatConfig } from '../config';
const featureTogglesJson = ` const featureTogglesJson = `
{ {
@ -13,7 +14,7 @@ const featureToggles = JSON.parse(featureTogglesJson);
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
export function FT(feature: string): boolean { export function FT(feature: string): boolean {
const betaInvitationCode = vscode.workspace.getConfiguration('DevChat').get<string>('betaInvitationCode'); const betaInvitationCode = new DevChatConfig().get('beta_invitation_code');
const expectedInvitationCode = 'WELCOMEADDTODEVCHAT'; const expectedInvitationCode = 'WELCOMEADDTODEVCHAT';
return betaInvitationCode === expectedInvitationCode || featureToggles[feature] === true; return betaInvitationCode === expectedInvitationCode || featureToggles[feature] === true;

View File

@ -9,6 +9,7 @@ import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import { UiUtilWrapper } from "../uiUtil"; import { UiUtilWrapper } from "../uiUtil";
import { DevChatConfig } from "../config";
import { getValidPythonCommand } from "../../contributes/commandsBase"; import { getValidPythonCommand } from "../../contributes/commandsBase";
@ -44,7 +45,7 @@ export async function installDevchat(): Promise<string> {
fs.writeFileSync(pythonPathFile, content); fs.writeFileSync(pythonPathFile, content);
// update DevChat.PythonForChat configration // update DevChat.PythonForChat configration
await UiUtilWrapper.updateConfiguration("DevChat", "PythonForChat", pythonApp); await new DevChatConfig().set("python_for_chat", pythonApp);
return pythonApp; return pythonApp;
} else { } else {
// if current os is not windows, we need to get default python path // if current os is not windows, we need to get default python path
@ -69,7 +70,7 @@ export async function installDevchat(): Promise<string> {
} }
logger.channel()?.info(`Create env success: ${pythonCommand}`); logger.channel()?.info(`Create env success: ${pythonCommand}`);
await UiUtilWrapper.updateConfiguration("DevChat", "PythonForChat", pythonCommand); await new DevChatConfig().set("python_for_chat", pythonCommand);
return pythonCommand; return pythonCommand;
} }
} catch (error) { } catch (error) {

View File

@ -2,8 +2,6 @@
export interface UiUtil { export interface UiUtil {
languageId(uri: string): Promise<string>; languageId(uri: string): Promise<string>;
workspaceFoldersFirstPath(): string | undefined; workspaceFoldersFirstPath(): string | undefined;
getConfiguration(key1: string, key2: string): string | undefined;
updateConfiguration(key1: string, key2: string, value: string): Promise<void>;
secretStorageGet(key: string): Promise<string | undefined>; secretStorageGet(key: string): Promise<string | undefined>;
writeFile(uri: string, content: string): Promise<void>; writeFile(uri: string, content: string): Promise<void>;
showInputBox(option: object): Promise<string | undefined>; showInputBox(option: object): Promise<string | undefined>;
@ -33,12 +31,6 @@ export class UiUtilWrapper {
public static workspaceFoldersFirstPath(): string | undefined { public static workspaceFoldersFirstPath(): string | undefined {
return this._uiUtil?.workspaceFoldersFirstPath(); return this._uiUtil?.workspaceFoldersFirstPath();
} }
public static getConfiguration(key1: string, key2: string): string | undefined {
return this._uiUtil?.getConfiguration(key1, key2);
}
public static async updateConfiguration(key1: string, key2: string, value: string): Promise<void> {
return await this._uiUtil?.updateConfiguration(key1, key2, value);
}
public static async secretStorageGet(key: string): Promise<string | undefined> { public static async secretStorageGet(key: string): Promise<string | undefined> {
return await this._uiUtil?.secretStorageGet(key); return await this._uiUtil?.secretStorageGet(key);
} }

View File

@ -14,19 +14,6 @@ export class UiUtilVscode implements UiUtil {
return vscode.workspace.workspaceFolders?.[0].uri.fsPath; return vscode.workspace.workspaceFolders?.[0].uri.fsPath;
} }
public getConfiguration(key1: string, key2: string): string | undefined {
return vscode.workspace.getConfiguration(key1).get(key2);
}
public async updateConfiguration(key1: string, key2: string, value: string): Promise<void> {
try {
await vscode.workspace.getConfiguration(key1).update(key2, value, vscode.ConfigurationTarget.Global);
await vscode.workspace.getConfiguration(key1).update(key2, value, vscode.ConfigurationTarget.Workspace);
} catch(error) {
return;
}
}
public async secretStorageGet(key: string): Promise<string | undefined> { public async secretStorageGet(key: string): Promise<string | undefined> {
try { try {
const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context!.secrets; const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context!.secrets;

View File

@ -1,153 +0,0 @@
import { expect } from 'chai';
import 'mocha';
import fs from 'fs';
import path from 'path';
import { CustomActions } from '../../src/action/customAction';
describe('CustomActions', () => {
const customActions = CustomActions.getInstance();
it('should return an empty action list', () => {
const actions = customActions.getActions();
expect(actions).to.deep.equal([]);
});
it('should return a non-empty action instruction with actions', () => {
// Add a sample action to the customActions instance
customActions.getActions().push({
name: 'sampleAction',
description: 'A sample action for testing',
type: ['test'],
action: 'sample',
handler: [],
args: [],
handlerAction: async (args: { [key: string]: string }) => {
return { exitCode: 0, stdout: '', stderr: '' };
},
});
const instruction = customActions.actionInstruction();
expect(instruction).to.include('sampleAction: A sample action for testing');
});
it('should return action instruction with args', () => {
// Add a sample action with args to the customActions instance
customActions.getActions().push({
name: 'sampleActionWithArgs',
description: 'A sample action with args for testing',
type: ['test'],
action: 'sample',
handler: [],
args: [
{
"name": "arg1",
"description": "Argument 1",
"type": "string",
"from": "content.fileName"
},
{
"name": "arg2",
"description": "Argument 2",
"type": "number",
"from": "content.content"
}
],
handlerAction: async (args: { [key: string]: string }) => {
return { exitCode: 0, stdout: '', stderr: '' };
},
});
const instruction = customActions.actionInstruction();
expect(instruction).to.include('sampleActionWithArgs: A sample action with args for testing');
expect(instruction).to.include('Args:');
expect(instruction).to.include('name: arg1 type: (string) description: Argument 1');
expect(instruction).to.include('name: arg2 type: (number) description: Argument 2');
});
it('should parse actions from workflows directory', () => {
// Create a temporary workflows directory with a sample action
const workflowsDir = path.join(__dirname, 'temp_workflows');
fs.mkdirSync(workflowsDir);
fs.mkdirSync(path.join(workflowsDir, 'sample_extension'));
fs.mkdirSync(path.join(workflowsDir, 'sample_extension', 'action'));
fs.mkdirSync(path.join(workflowsDir, 'sample_extension', 'action', 'sample_action'));
const settingsPath = path.join(workflowsDir, 'sample_extension', 'action', 'sample_action', '_setting_.json');
const sampleActionSettings = {
name: 'sampleParsedAction',
description: 'A sample parsed action for testing',
type: ['test'],
action: 'sample',
handler: [],
args: [],
};
fs.writeFileSync(settingsPath, JSON.stringify(sampleActionSettings));
// Call parseActions with the temporary workflows directory
customActions.parseActions(workflowsDir);
// Check if the parsed action is in the actions list
const actions = customActions.getActions();
const parsedAction = actions.find(action => action.name === 'sampleParsedAction');
expect(parsedAction).to.not.be.undefined;
expect(parsedAction?.description).to.equal('A sample parsed action for testing');
// Clean up the temporary workflows directory
fs.unlinkSync(settingsPath);
fs.rmdirSync(path.join(workflowsDir, 'sample_extension', 'action', 'sample_action'));
fs.rmdirSync(path.join(workflowsDir, 'sample_extension', 'action'));
fs.rmdirSync(path.join(workflowsDir, 'sample_extension'));
fs.rmdirSync(workflowsDir);
});
it('should parse handlerAction correctly from directory with echo command', async () => {
// Create a temporary directory for the sample action
const workflowsDir = path.join(__dirname, 'temp_workflows');
fs.mkdirSync(workflowsDir);
fs.mkdirSync(path.join(workflowsDir, 'sample_extension'));
fs.mkdirSync(path.join(workflowsDir, 'sample_extension', 'action'));
fs.mkdirSync(path.join(workflowsDir, 'sample_extension', 'action', 'sample_action'));
const settingsPath = path.join(workflowsDir, 'sample_extension', 'action', 'sample_action', '_setting_.json');
const sampleActionJson = {
name: 'sampleAction',
description: 'A sample action with a handlerAction method for testing',
type: ['test'],
action: 'sample',
handler: ["echo", "${arg1}"],
args: [
{ name: 'arg1', type: 'string' },
{ name: 'arg2', type: 'string' },
],
};
fs.writeFileSync(settingsPath, JSON.stringify(sampleActionJson));
// Call parseActions with the temporary directory
customActions.parseActions(workflowsDir);
const actions = customActions.getActions();
// Clean up the temporary directory
fs.unlinkSync(settingsPath);
fs.rmdirSync(path.join(workflowsDir, 'sample_extension', 'action', 'sample_action'));
fs.rmdirSync(path.join(workflowsDir, 'sample_extension', 'action'));
fs.rmdirSync(path.join(workflowsDir, 'sample_extension'));
fs.rmdirSync(workflowsDir);
// Check if the returned actions array has the expected length
expect(actions.length).equal(1);
// Get the parsed action object
const parsedAction = actions[0];
// Call the handlerAction method with valid args
const validResult = await parsedAction.handlerAction({ arg1: 'value1', arg2: 'value2' });
// Check if the returned CommandResult is as expected
expect(validResult).to.deep.equal({ exitCode: 0, stdout: 'value1\n', stderr: '' });
// Call the handlerAction method with invalid args
const invalidResult = await parsedAction.handlerAction({ arg1: 'wrongValue', arg2: 'value2' });
// Check if the returned CommandResult is as expected
expect(invalidResult).to.deep.equal({ exitCode: 0, stdout: 'wrongValue\n', stderr: '' });
});
});

View File

@ -1,31 +0,0 @@
// test/apiKey.test.ts
import { expect } from 'chai';
import { ApiKeyManager } from '../../src/util/apiKey';
import { UiUtilWrapper } from '../../src/util/uiUtil';
import sinon from 'sinon';
describe('ApiKeyManager', () => {
afterEach(() => {
sinon.restore();
delete process.env.OPENAI_API_KEY;
delete process.env.OPENAI_API_BASE;
});
describe('getKeyType', () => {
it('should return "sk" for sk keys', () => {
const keyType = ApiKeyManager.getKeyType('sk-key');
expect(keyType).to.equal('sk');
});
it('should return "DC" for DC keys', () => {
const keyType = ApiKeyManager.getKeyType('DC.key');
expect(keyType).to.equal('DC');
});
it('should return undefined for invalid keys', () => {
const keyType = ApiKeyManager.getKeyType('invalid.key');
expect(keyType).to.be.undefined;
});
});
});

69
test/util/config.test.ts Normal file
View File

@ -0,0 +1,69 @@
import { expect } from 'chai';
import { describe, it, beforeEach, afterEach } from 'mocha';
import fs from 'fs';
import yaml from 'yaml';
import { DevChatConfig } from '../../src/util/config'; // 调整路径以指向config.ts的实际位置
import sinon from 'sinon';
import { logger } from '../../src/util/logger'; // 调整路径以指向logger的实际位置
describe('DevChatConfig', () => {
let readFileStub: sinon.SinonStub;
let writeFileStub: sinon.SinonStub;
let loggerStub: sinon.SinonStub;
const mockData = {
username: 'DevUser',
theme: 'dark',
};
beforeEach(() => {
// Mock fs.readFileSync to return a YAML string based on mockData
readFileStub = sinon.stub(fs, 'readFileSync').returns(yaml.stringify(mockData));
// Mock fs.writeFileSync to fake the writing process
writeFileStub = sinon.stub(fs, 'writeFileSync');
// Mock the logger to prevent logging during tests
loggerStub = sinon.stub(logger, 'channel').callsFake(() => ({
info: sinon.fake(),
warn: sinon.fake(),
error: sinon.fake(),
debug: sinon.fake(),
show: sinon.fake(),
}));
});
afterEach(() => {
// Restore the original functionalities
readFileStub.restore();
writeFileStub.restore();
loggerStub.restore();
});
it('should read config file and get the correct value for a given key', () => {
const config = new DevChatConfig();
expect(config.get('username')).to.equal('DevUser');
});
it('should set a new key-value pair and write to the config file', () => {
const config = new DevChatConfig();
const newKey = 'notifications.enabled';
const newValue = true;
config.set(newKey, newValue);
expect(config.get('notifications.enabled')).to.equal(true);
// Check if fs.writeFileSync was called
sinon.assert.calledOnce(writeFileStub);
});
it('should handle errors when reading an invalid config file', () => {
readFileStub.throws(new Error('Failed to read file'));
// Constructing the config will attempt to read the file and log an error
const config = new DevChatConfig();
// Check if the error was logged
sinon.assert.called(loggerStub);
});
});