From 2b505b553680e8985dda9630669e97d4a37c447e Mon Sep 17 00:00:00 2001 From: "bobo.yang" Date: Wed, 17 Apr 2024 00:07:09 +0800 Subject: [PATCH] Refactor DevChatConfig instantiation to use getInstance() method --- .../codecomplete/astIndex/index.ts | 2 +- src/contributes/codecomplete/codecomplete.ts | 2 +- src/contributes/codecomplete/llm.ts | 33 ++++++++++++++----- src/contributes/codecomplete/promptCreator.ts | 10 +++++- .../codecomplete/symbolindex/index.ts | 2 +- src/contributes/commands.ts | 6 ++-- src/contributes/commandsBase.ts | 2 +- src/extension.ts | 2 +- src/handler/configHandler.ts | 8 ++--- src/handler/historyMessagesHandler.ts | 2 +- src/ide_services/endpoints/ideLanguage.ts | 2 +- src/panel/statusBarView.ts | 2 +- src/toolwrapper/devchat.ts | 10 +++--- src/util/apiKey.ts | 2 +- src/util/config.ts | 11 ++++++- src/util/feature_flags/feature_toggles.ts | 2 +- src/util/python_installer/install_devchat.ts | 4 +-- test/util/config.test.ts | 6 ++-- 18 files changed, 70 insertions(+), 38 deletions(-) diff --git a/src/contributes/codecomplete/astIndex/index.ts b/src/contributes/codecomplete/astIndex/index.ts index 7819be9..390960e 100644 --- a/src/contributes/codecomplete/astIndex/index.ts +++ b/src/contributes/codecomplete/astIndex/index.ts @@ -12,7 +12,7 @@ import { createFileBlockInfo, createIdentifierSetByQuery } from './createIdentif let indexStore: IndexStore | undefined = undefined; -const devchatConfig = new DevChatConfig(); +const devchatConfig = DevChatConfig.getInstance(); export const BLACK_LIST_DIRS = [ "node_modules", diff --git a/src/contributes/codecomplete/codecomplete.ts b/src/contributes/codecomplete/codecomplete.ts index 57edc37..4658738 100644 --- a/src/contributes/codecomplete/codecomplete.ts +++ b/src/contributes/codecomplete/codecomplete.ts @@ -61,7 +61,7 @@ export class InlineCompletionProvider implements vscode.InlineCompletionItemProv // Read delay time from config this.debouncer = new Debouncer(500); this.cache = new MemoryCacheManager(); - this.devchatConfig = new DevChatConfig(); + this.devchatConfig = DevChatConfig.getInstance(); this.lastComplete = ""; this.recentEditors = new RecentEditsManager(); } diff --git a/src/contributes/codecomplete/llm.ts b/src/contributes/codecomplete/llm.ts index c1af705..d3f9bd5 100644 --- a/src/contributes/codecomplete/llm.ts +++ b/src/contributes/codecomplete/llm.ts @@ -16,15 +16,23 @@ export interface CodeCompletionChunk { } export async function* streamComplete(prompt: string): AsyncGenerator { - for await (const chunk of nvidiaStarcoderComplete(prompt)) { - yield chunk; + const nvidiaKey = DevChatConfig.getInstance().get("complete_key"); + const ollamaApiBase = DevChatConfig.getInstance().get("complete_ollama_api_base"); + if (ollamaApiBase) { + for await (const chunk of ollamaDeepseekComplete(prompt)) { + yield chunk; + } + } else if (nvidiaKey) { + for await (const chunk of nvidiaStarcoderComplete(prompt)) { + yield chunk; + } } } export async function * nvidiaStarcoderComplete(prompt: string) : AsyncGenerator { const invokeUrl = 'https://api.nvcf.nvidia.com/v2/nvcf/pexec/functions/6acada03-fe2f-4e4d-9e0a-e711b9fd1b59'; - const nvidiaKey = new DevChatConfig().get("complete_key"); + const nvidiaKey = DevChatConfig.getInstance().get("complete_key"); if (!nvidiaKey) { return; } @@ -89,17 +97,24 @@ export async function * nvidiaStarcoderComplete(prompt: string) : AsyncGenerator } } -export async function * ollamaStarcoderComplete(prompt: string) : AsyncGenerator { - const url = 'http://192.168.1.138:11434/api/generate'; +export async function * ollamaDeepseekComplete(prompt: string) : AsyncGenerator { + const ollamaApiBase = DevChatConfig.getInstance().get("complete_ollama_api_base"); + if (!ollamaApiBase) { + return; + } + + const urlBase = ollamaApiBase.trim().endsWith('/') ? ollamaApiBase : ollamaApiBase + '/'; + const url = urlBase + 'api/generate'; + const headers = { 'Content-Type': 'application/json', }; const payload = { - model: 'starcoder:7b', + model: 'deepseek-coder:6.7b-base', prompt: prompt, stream: true, options: { - stop: ["<|endoftext|>", "", "```", "\n\n"], + stop: ["<|endoftext|>", "<|EOT|>", "", "```", "/", "\n\n"], temperature: 0.2 } }; @@ -131,8 +146,8 @@ export async function * ollamaStarcoderComplete(prompt: string) : AsyncGenerator id: idResponse! }; } catch (e: any) { - logger.channel()?.info("receve:", chunkText); - logger.channel()?.error("JSON Parsing Error:", e.message); + // logger.channel()?.info("receive:", chunkText); + logger.channel()?.warn("JSON Parsing fail:", e.message); } } } else { diff --git a/src/contributes/codecomplete/promptCreator.ts b/src/contributes/codecomplete/promptCreator.ts index 066956b..4472c9b 100644 --- a/src/contributes/codecomplete/promptCreator.ts +++ b/src/contributes/codecomplete/promptCreator.ts @@ -24,6 +24,7 @@ import MemoryCacheManager from "./cache"; import { searchSimilarBlock } from "./astIndex"; import { GitDiffWatcher } from "./gitDiffWatcher"; import { findIdentifiersInAstNodeRange } from './ast/findIdentifiers'; +import { DevChatConfig } from '../../util/config'; const CONTEXT_LIMITED_SIZE: number = 6000; @@ -674,7 +675,14 @@ export async function createPrompt(filePath: string, fileContent: string, line: logger.channel()?.info("Complete token:", tokenCount); - const prompt = "" + taskDescriptionContextWithCommentPrefix + neighborFileContext + recentEditContext + symbolContext + callDefContext + similarBlockContext + gitDiffContext + `${commentPrefix}${filePath}\n\n` + prefix + "" + suffix + ""; + let prompt = ""; + const ollamaApiBase = DevChatConfig.getInstance().get("complete_ollama_api_base"); + if (ollamaApiBase) { + prompt = "<|fim▁begin|>" + taskDescriptionContextWithCommentPrefix + neighborFileContext + recentEditContext + symbolContext + callDefContext + similarBlockContext + gitDiffContext + `${commentPrefix}${filePath}\n\n` + prefix + "<|fim▁hole|>" + suffix + "<|fim▁end|>"; + } else { + prompt = "" + taskDescriptionContextWithCommentPrefix + neighborFileContext + recentEditContext + symbolContext + callDefContext + similarBlockContext + gitDiffContext + `${commentPrefix}${filePath}\n\n` + prefix + "" + suffix + ""; + } + return prompt; } diff --git a/src/contributes/codecomplete/symbolindex/index.ts b/src/contributes/codecomplete/symbolindex/index.ts index ef56a47..ae23ee0 100644 --- a/src/contributes/codecomplete/symbolindex/index.ts +++ b/src/contributes/codecomplete/symbolindex/index.ts @@ -25,7 +25,7 @@ import { DevChatConfig } from '../../../util/config'; // 记录文件的最后修改时间 const lastModified = new Map(); -const devchatConfig = new DevChatConfig(); +const devchatConfig = DevChatConfig.getInstance(); const BLACK_LIST_DIRS = [ "node_modules", diff --git a/src/contributes/commands.ts b/src/contributes/commands.ts index 8dc1bb4..67c2906 100644 --- a/src/contributes/commands.ts +++ b/src/contributes/commands.ts @@ -145,7 +145,7 @@ export function regPythonPathCommand(context: vscode.ExtensionContext) { })) ?? ""; if (pythonPath) { - new DevChatConfig().set("python_for_chat", pythonPath); + DevChatConfig.getInstance().set("python_for_chat", pythonPath); } }) ); @@ -290,7 +290,7 @@ export function registerInstallCommandsPython(context: vscode.ExtensionContext) return ''; } - new DevChatConfig().set("python_for_commands", pythonCommand.trim()); + DevChatConfig.getInstance().set("python_for_commands", pythonCommand.trim()); // vscode.window.showInformationMessage(`All slash Commands are ready to use! Please input / to try workflow commands!`); }); @@ -352,7 +352,7 @@ export function registerHandleUri(context: vscode.ExtensionContext) { // 解析 URI 并执行相应的操作 if (uri.path.includes("accesskey")) { const accessKey = uri.path.split("/")[2]; - new DevChatConfig().set("provides.devchat.api_key", accessKey); + DevChatConfig.getInstance().set("provides.devchat.api_key", accessKey); ensureChatPanel(context); await new Promise((resolve, reject) => { setTimeout(() => { diff --git a/src/contributes/commandsBase.ts b/src/contributes/commandsBase.ts index 56f0af4..fdaf615 100644 --- a/src/contributes/commandsBase.ts +++ b/src/contributes/commandsBase.ts @@ -43,7 +43,7 @@ function getDefaultPythonCommand(): string | undefined { export function getValidPythonCommand(): string | undefined { try { - const devchatConfig = new DevChatConfig(); + const devchatConfig = DevChatConfig.getInstance(); const pythonCommand = devchatConfig.get('python_for_chat'); if (pythonCommand) { return pythonCommand; diff --git a/src/extension.ts b/src/extension.ts index 28bb127..0f20ec0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -36,7 +36,7 @@ import { indexDir } from "./contributes/codecomplete/astIndex"; async function migrateConfig() { - const devchatConfig = new DevChatConfig(); + const devchatConfig = DevChatConfig.getInstance(); const devchatProvider = "providers.devchat"; const devchatProviderConfig: any = devchatConfig.get(devchatProvider); if (devchatProviderConfig) { diff --git a/src/handler/configHandler.ts b/src/handler/configHandler.ts index ebe36a2..56d6769 100644 --- a/src/handler/configHandler.ts +++ b/src/handler/configHandler.ts @@ -11,10 +11,10 @@ regInMessage({command: 'readConfig', key: ['A','B']}); // when key is "", it wil regOutMessage({command: 'readConfig', key: ['A', 'B'], value: 'any'}); export async function readConfig(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise { if (message.key === '' || message.key === '*' || message.key.length === 0 || message.key[1] === '*') { - const config = new DevChatConfig().getAll(); + const config = DevChatConfig.getInstance().getAll(); MessageHandler.sendMessage(panel, {command: 'readConfig', key: message.key, value: config}); } else { - const config = new DevChatConfig().get(message.key); + const config = DevChatConfig.getInstance().get(message.key); MessageHandler.sendMessage(panel, {command: 'readConfig', key: message.key, value: config}); } } @@ -22,8 +22,8 @@ export async function readConfig(message: any, panel: vscode.WebviewPanel|vscode regInMessage({command: 'writeConfig', key: ['A', 'B'], value: 'any'}); // when key is "", it will rewrite all config values export async function writeConfig(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise { if (message.key === '' || message.key === '*' || message.key.length === 0 || message.key[1] === '*') { - new DevChatConfig().setAll(message.value); + DevChatConfig.getInstance().setAll(message.value); } else { - new DevChatConfig().set(message.key, message.value); + DevChatConfig.getInstance().set(message.key, message.value); } } \ No newline at end of file diff --git a/src/handler/historyMessagesHandler.ts b/src/handler/historyMessagesHandler.ts index 55a5b4e..b719aea 100644 --- a/src/handler/historyMessagesHandler.ts +++ b/src/handler/historyMessagesHandler.ts @@ -11,7 +11,7 @@ regInMessage({command: 'historyMessages', topicId: '', page: 0}); 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 { // if history message has load, send it to webview - const maxCount = Number(new DevChatConfig().get('max_log_count')); + const maxCount = Number(DevChatConfig.getInstance().get('max_log_count')); const skip = maxCount * (message.page ? message.page : 0); const topicId = message.topicId; diff --git a/src/ide_services/endpoints/ideLanguage.ts b/src/ide_services/endpoints/ideLanguage.ts index 947120f..8095e60 100644 --- a/src/ide_services/endpoints/ideLanguage.ts +++ b/src/ide_services/endpoints/ideLanguage.ts @@ -1,7 +1,7 @@ import { DevChatConfig } from "../../util/config"; export async function ideLanguage() { - const language = new DevChatConfig().get('language'); + const language = DevChatConfig.getInstance().get('language'); // 'en' stands for English, 'zh' stands for Simplified Chinese return language; } diff --git a/src/panel/statusBarView.ts b/src/panel/statusBarView.ts index 230b401..e46fe03 100644 --- a/src/panel/statusBarView.ts +++ b/src/panel/statusBarView.ts @@ -25,7 +25,7 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St function checkDevChatCommandsStatus() { const timerDevchatCommands = setInterval(async () => { try { - const pythonCommand = new DevChatConfig().get('python_for_commands'); + const pythonCommand = DevChatConfig.getInstance().get('python_for_commands'); if (!pythonCommand) { statusBarItem.text = `$(pass)DevChat$(warning)`; statusBarItem.tooltip = `ready to chat, command functionality limited`; diff --git a/src/toolwrapper/devchat.ts b/src/toolwrapper/devchat.ts index 82f614b..aaea5c5 100644 --- a/src/toolwrapper/devchat.ts +++ b/src/toolwrapper/devchat.ts @@ -123,7 +123,7 @@ class DevChat { assertValue(!llmModelData || !llmModelData.model, 'You must select a LLM model to use for conversations'); args.push("-m", llmModelData.model); - const functionCalling = new DevChatConfig().get('enable_function_calling'); + const functionCalling = DevChatConfig.getInstance().get('enable_function_calling'); if (functionCalling) { args.push("-a"); } @@ -140,7 +140,7 @@ class DevChat { if (options.maxCount) { args.push('--max-count', `${options.maxCount}`); } else { - const maxLogCount = new DevChatConfig().get('max_log_count'); + const maxLogCount = DevChatConfig.getInstance().get('max_log_count'); args.push('--max-count', `${maxLogCount}`); } @@ -211,7 +211,7 @@ class DevChat { "PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages" }; - const pythonApp = new DevChatConfig().get('python_for_chat') || "python3"; + const pythonApp = DevChatConfig.getInstance().get('python_for_chat') || "python3"; // run command const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync( @@ -251,7 +251,7 @@ class DevChat { // eslint-disable-next-line @typescript-eslint/naming-convention "PYTHONUTF8": 1, // eslint-disable-next-line @typescript-eslint/naming-convention - "command_python": new DevChatConfig().get('python_for_commands') || "", + "command_python": DevChatConfig.getInstance().get('python_for_commands') || "", // eslint-disable-next-line @typescript-eslint/naming-convention "PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages", // eslint-disable-next-line @typescript-eslint/naming-convention @@ -279,7 +279,7 @@ class DevChat { onData(data); }; // run command - const pythonApp = new DevChatConfig().get('python_for_chat') || "python3"; + const pythonApp = DevChatConfig.getInstance().get('python_for_chat') || "python3"; logger.channel()?.info(`Running devchat:${pythonApp} ${args.join(" ")}`); const { exitCode: code, stdout, stderr } = await this.commandRun.spawnAsync(pythonApp, args, spawnAsyncOptions, onStdoutPartial, undefined, undefined, undefined); // handle result diff --git a/src/util/apiKey.ts b/src/util/apiKey.ts index 6c6bcdd..eece373 100644 --- a/src/util/apiKey.ts +++ b/src/util/apiKey.ts @@ -7,7 +7,7 @@ import { logger } from './logger'; export class ApiKeyManager { static async llmModel() { - const devchatConfig = new DevChatConfig(); + const devchatConfig = DevChatConfig.getInstance(); const defaultModel = devchatConfig.get('default_model'); if (!defaultModel) { return undefined; diff --git a/src/util/config.ts b/src/util/config.ts index c67d1ef..e90601d 100644 --- a/src/util/config.ts +++ b/src/util/config.ts @@ -5,18 +5,27 @@ import { logger } from './logger'; export class DevChatConfig { + private static instance: DevChatConfig; + // 配置文件路径,根据操作系统的差异,可能需要调整 private configFilePath: string; private data: any; // last modify timestamp of the config file private lastModifyTime: number; - constructor() { + private constructor() { // 视操作系统的差异,可能需要调整路径 ~/.chat/config.yml this.configFilePath = path.join(process.env.HOME || process.env.USERPROFILE || '', '.chat', 'config.yml'); this.lastModifyTime = 0; this.readConfigFile(); } + public static getInstance(): DevChatConfig { + if (!DevChatConfig.instance) { + DevChatConfig.instance = new DevChatConfig(); + } + return DevChatConfig.instance; + } + private readConfigFile() { try { const fileContents = fs.readFileSync(this.configFilePath, 'utf8'); diff --git a/src/util/feature_flags/feature_toggles.ts b/src/util/feature_flags/feature_toggles.ts index 413710c..c2eac2d 100644 --- a/src/util/feature_flags/feature_toggles.ts +++ b/src/util/feature_flags/feature_toggles.ts @@ -14,7 +14,7 @@ const featureToggles = JSON.parse(featureTogglesJson); // eslint-disable-next-line @typescript-eslint/naming-convention export function FT(feature: string): boolean { - const betaInvitationCode = new DevChatConfig().get('beta_invitation_code'); + const betaInvitationCode = DevChatConfig.getInstance().get('beta_invitation_code'); const expectedInvitationCode = 'WELCOMEADDTODEVCHAT'; return betaInvitationCode === expectedInvitationCode || featureToggles[feature] === true; diff --git a/src/util/python_installer/install_devchat.ts b/src/util/python_installer/install_devchat.ts index 7c8db28..d3faa57 100644 --- a/src/util/python_installer/install_devchat.ts +++ b/src/util/python_installer/install_devchat.ts @@ -45,7 +45,7 @@ export async function installDevchat(): Promise { fs.writeFileSync(pythonPathFile, content); // update DevChat.PythonForChat configration - await new DevChatConfig().set("python_for_chat", pythonApp); + await DevChatConfig.getInstance().set("python_for_chat", pythonApp); return pythonApp; } else { // if current os is not windows, we need to get default python path @@ -70,7 +70,7 @@ export async function installDevchat(): Promise { } logger.channel()?.info(`Create env success: ${pythonCommand}`); - await new DevChatConfig().set("python_for_chat", pythonCommand); + await DevChatConfig.getInstance().set("python_for_chat", pythonCommand); return pythonCommand; } } catch (error) { diff --git a/test/util/config.test.ts b/test/util/config.test.ts index e3e018c..9dbefa4 100644 --- a/test/util/config.test.ts +++ b/test/util/config.test.ts @@ -41,12 +41,12 @@ describe('DevChatConfig', () => { }); it('should read config file and get the correct value for a given key', () => { - const config = new DevChatConfig(); + const config = DevChatConfig.getInstance(); 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 config = DevChatConfig.getInstance(); const newKey = 'notifications.enabled'; const newValue = true; @@ -61,7 +61,7 @@ describe('DevChatConfig', () => { 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(); + const config = DevChatConfig.getInstance(); // Check if the error was logged sinon.assert.called(loggerStub);