Separate VSCode from core logic for ISSUE #125
- Extract VSCode-related code from core logic. - Improve modularity for easier unit testing.
This commit is contained in:
parent
562a125902
commit
82faf14eb6
@ -9,6 +9,8 @@
|
|||||||
"@typescript-eslint"
|
"@typescript-eslint"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"no-unused-vars": "warn",
|
||||||
|
"no-unused-expressions": "warn",
|
||||||
"@typescript-eslint/naming-convention": "warn",
|
"@typescript-eslint/naming-convention": "warn",
|
||||||
"@typescript-eslint/semi": "warn",
|
"@typescript-eslint/semi": "warn",
|
||||||
"curly": "warn",
|
"curly": "warn",
|
||||||
|
@ -4,6 +4,7 @@ import { createTempSubdirectory, runCommandStringAndWriteOutput } from '../util/
|
|||||||
import { logger } from '../util/logger';
|
import { logger } from '../util/logger';
|
||||||
import { UiUtilWrapper } from '../util/uiUtil';
|
import { UiUtilWrapper } from '../util/uiUtil';
|
||||||
|
|
||||||
|
|
||||||
export const customCommandContext: ChatContext = {
|
export const customCommandContext: ChatContext = {
|
||||||
name: '<custom command>',
|
name: '<custom command>',
|
||||||
description: 'custorm command',
|
description: 'custorm command',
|
||||||
@ -17,15 +18,15 @@ export const customCommandContext: ChatContext = {
|
|||||||
// 检查用户是否输入了命令
|
// 检查用户是否输入了命令
|
||||||
if (customCommand) {
|
if (customCommand) {
|
||||||
const tempDir = await createTempSubdirectory('devchat/context');
|
const tempDir = await createTempSubdirectory('devchat/context');
|
||||||
const diff_file = path.join(tempDir, 'custom.txt');
|
const diffFile = path.join(tempDir, 'custom.txt');
|
||||||
|
|
||||||
logger.channel()?.info(`custom command: ${customCommand}`);
|
logger.channel()?.info(`custom command: ${customCommand}`);
|
||||||
const result = await runCommandStringAndWriteOutput(customCommand, diff_file);
|
const result = await runCommandStringAndWriteOutput(customCommand, diffFile);
|
||||||
logger.channel()?.info(`custom command: ${customCommand} exit code:`, result.exitCode);
|
logger.channel()?.info(`custom command: ${customCommand} exit code:`, result.exitCode);
|
||||||
|
|
||||||
logger.channel()?.debug(`custom command: ${customCommand} stdout:`, result.stdout);
|
logger.channel()?.debug(`custom command: ${customCommand} stdout:`, result.stdout);
|
||||||
logger.channel()?.debug(`custom command: ${customCommand} stderr:`, result.stderr);
|
logger.channel()?.debug(`custom command: ${customCommand} stderr:`, result.stderr);
|
||||||
return `[context|${diff_file}]`;
|
return `[context|${diffFile}]`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
|
@ -1,57 +1,11 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { sendFileSelectMessage, sendCodeSelectMessage } from './util';
|
import { sendFileSelectMessage, sendCodeSelectMessage } from './util';
|
||||||
import { logger } from '../util/logger';
|
|
||||||
import * as childProcess from 'child_process';
|
|
||||||
import ExtensionContextHolder from '../util/extensionContext';
|
import ExtensionContextHolder from '../util/extensionContext';
|
||||||
import { TopicManager, Topic } from '../topic/topicManager';
|
import { TopicManager } from '../topic/topicManager';
|
||||||
import { TopicTreeDataProvider, TopicTreeItem } from '../panel/topicView';
|
import { TopicTreeDataProvider, TopicTreeItem } from '../panel/topicView';
|
||||||
import { FilePairManager } from '../util/diffFilePairs';
|
import { FilePairManager } from '../util/diffFilePairs';
|
||||||
|
|
||||||
|
|
||||||
import * as process from 'process';
|
|
||||||
import { UiUtilWrapper } from '../util/uiUtil';
|
|
||||||
|
|
||||||
|
|
||||||
export function checkDevChatDependency(): boolean {
|
|
||||||
try {
|
|
||||||
// Get pipx environment
|
|
||||||
const pipxEnvOutput = childProcess.execSync('python3 -m pipx environment').toString();
|
|
||||||
const binPathRegex = /PIPX_BIN_DIR=\s*(.*)/;
|
|
||||||
|
|
||||||
// Get BIN path from pipx environment
|
|
||||||
const match = pipxEnvOutput.match(binPathRegex);
|
|
||||||
if (match && match[1]) {
|
|
||||||
const binPath = match[1];
|
|
||||||
|
|
||||||
// Add BIN path to PATH
|
|
||||||
process.env.PATH = `${binPath}:${process.env.PATH}`;
|
|
||||||
|
|
||||||
// Check if DevChat is installed
|
|
||||||
childProcess.execSync('devchat --help');
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// DevChat dependency check failed
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkOpenAiAPIKey() {
|
|
||||||
let openaiApiKey = await UiUtilWrapper.secretStorageGet("devchat_OPENAI_API_KEY");
|
|
||||||
if (!openaiApiKey) {
|
|
||||||
openaiApiKey = UiUtilWrapper.getConfiguration('DevChat', 'API_KEY');
|
|
||||||
}
|
|
||||||
if (!openaiApiKey) {
|
|
||||||
openaiApiKey = process.env.OPENAI_API_KEY;
|
|
||||||
}
|
|
||||||
if (!openaiApiKey) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerOpenChatPanelCommand(context: vscode.ExtensionContext) {
|
function registerOpenChatPanelCommand(context: vscode.ExtensionContext) {
|
||||||
let disposable = vscode.commands.registerCommand('devchat.openChatPanel', async () => {
|
let disposable = vscode.commands.registerCommand('devchat.openChatPanel', async () => {
|
||||||
await vscode.commands.executeCommand('devchat-view.focus');
|
await vscode.commands.executeCommand('devchat-view.focus');
|
||||||
@ -65,27 +19,19 @@ async function ensureChatPanel(context: vscode.ExtensionContext): Promise<boolea
|
|||||||
}
|
}
|
||||||
|
|
||||||
function registerAddContextCommand(context: vscode.ExtensionContext) {
|
function registerAddContextCommand(context: vscode.ExtensionContext) {
|
||||||
const disposableAddContext = vscode.commands.registerCommand('devchat.addConext', async (uri: { path: any; }) => {
|
const callback = async (uri: { path: any; }) => {
|
||||||
if (!await ensureChatPanel(context)) {
|
if (!await ensureChatPanel(context)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, uri.path);
|
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, uri.path);
|
||||||
});
|
};
|
||||||
context.subscriptions.push(disposableAddContext);
|
context.subscriptions.push(vscode.commands.registerCommand('devchat.addConext', callback));
|
||||||
|
context.subscriptions.push(vscode.commands.registerCommand('devchat.addConext_chinese', callback));
|
||||||
const disposableAddContextChinese = vscode.commands.registerCommand('devchat.addConext_chinese', async (uri: { path: any; }) => {
|
|
||||||
if (!await ensureChatPanel(context)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, uri.path);
|
|
||||||
});
|
|
||||||
context.subscriptions.push(disposableAddContextChinese);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerAskForCodeCommand(context: vscode.ExtensionContext) {
|
function registerAskForCodeCommand(context: vscode.ExtensionContext) {
|
||||||
const disposableCodeContext = vscode.commands.registerCommand('devchat.askForCode', async () => {
|
const callback = async () => {
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
if (editor) {
|
if (editor) {
|
||||||
if (!await ensureChatPanel(context)) {
|
if (!await ensureChatPanel(context)) {
|
||||||
@ -95,25 +41,13 @@ function registerAskForCodeCommand(context: vscode.ExtensionContext) {
|
|||||||
const selectedText = editor.document.getText(editor.selection);
|
const selectedText = editor.document.getText(editor.selection);
|
||||||
await sendCodeSelectMessage(ExtensionContextHolder.provider?.view()!, editor.document.fileName, selectedText);
|
await sendCodeSelectMessage(ExtensionContextHolder.provider?.view()!, editor.document.fileName, selectedText);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
context.subscriptions.push(disposableCodeContext);
|
context.subscriptions.push(vscode.commands.registerCommand('devchat.askForCode', callback));
|
||||||
|
context.subscriptions.push(vscode.commands.registerCommand('devchat.askForCode_chinese', callback));
|
||||||
const disposableCodeContextChinese = vscode.commands.registerCommand('devchat.askForCode_chinese', async () => {
|
|
||||||
const editor = vscode.window.activeTextEditor;
|
|
||||||
if (editor) {
|
|
||||||
if (!await ensureChatPanel(context)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedText = editor.document.getText(editor.selection);
|
|
||||||
await sendCodeSelectMessage(ExtensionContextHolder.provider?.view()!, editor.document.fileName, selectedText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
context.subscriptions.push(disposableCodeContextChinese);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerAskForFileCommand(context: vscode.ExtensionContext) {
|
function registerAskForFileCommand(context: vscode.ExtensionContext) {
|
||||||
const disposableAskFile = vscode.commands.registerCommand('devchat.askForFile', async () => {
|
const callback = async () => {
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
if (editor) {
|
if (editor) {
|
||||||
if (!await ensureChatPanel(context)) {
|
if (!await ensureChatPanel(context)) {
|
||||||
@ -122,20 +56,9 @@ function registerAskForFileCommand(context: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, editor.document.fileName);
|
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, editor.document.fileName);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
context.subscriptions.push(disposableAskFile);
|
context.subscriptions.push(vscode.commands.registerCommand('devchat.askForFile', callback));
|
||||||
|
context.subscriptions.push(vscode.commands.registerCommand('devchat.askForFile_chinese', callback));
|
||||||
const disposableAskFileChinese = vscode.commands.registerCommand('devchat.askForFile_chinese', async () => {
|
|
||||||
const editor = vscode.window.activeTextEditor;
|
|
||||||
if (editor) {
|
|
||||||
if (!await ensureChatPanel(context)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await sendFileSelectMessage(ExtensionContextHolder.provider?.view()!, editor.document.fileName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
context.subscriptions.push(disposableAskFileChinese);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerApiKeySettingCommand(context: vscode.ExtensionContext) {
|
export function registerApiKeySettingCommand(context: vscode.ExtensionContext) {
|
||||||
@ -160,11 +83,27 @@ export function registerStatusBarItemClickCommand(context: vscode.ExtensionConte
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const topicDeleteCallback = async (item: TopicTreeItem) => {
|
||||||
|
const confirm = 'Delete';
|
||||||
|
const cancel = 'Cancel';
|
||||||
|
const label = typeof item.label === 'string' ? item.label : item.label!.label;
|
||||||
|
const truncatedLabel = label.substring(0, 20) + (label.length > 20 ? '...' : '');
|
||||||
|
const result = await vscode.window.showWarningMessage(
|
||||||
|
`Are you sure you want to delete the topic "${truncatedLabel}"?`,
|
||||||
|
{ modal: true },
|
||||||
|
confirm,
|
||||||
|
cancel
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result === confirm) {
|
||||||
|
TopicManager.getInstance().deleteTopic(item.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
;
|
||||||
|
|
||||||
export function regTopicDeleteCommand(context: vscode.ExtensionContext) {
|
export function regTopicDeleteCommand(context: vscode.ExtensionContext) {
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
vscode.commands.registerCommand('devchat-topicview.deleteTopic', (item: TopicTreeItem) => {
|
vscode.commands.registerCommand('devchat-topicview.deleteTopic', topicDeleteCallback)
|
||||||
TopicManager.getInstance().deleteTopic(item.id);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +121,7 @@ export function regDeleteSelectTopicCommand(context: vscode.ExtensionContext) {
|
|||||||
vscode.commands.registerCommand('devchat-topicview.deleteSelectedTopic', () => {
|
vscode.commands.registerCommand('devchat-topicview.deleteSelectedTopic', () => {
|
||||||
const selectedItem = TopicTreeDataProvider.getInstance().selectedItem;
|
const selectedItem = TopicTreeDataProvider.getInstance().selectedItem;
|
||||||
if (selectedItem) {
|
if (selectedItem) {
|
||||||
TopicManager.getInstance().deleteTopic(selectedItem.id);
|
topicDeleteCallback(selectedItem);
|
||||||
} else {
|
} else {
|
||||||
vscode.window.showErrorMessage('No item selected');
|
vscode.window.showErrorMessage('No item selected');
|
||||||
}
|
}
|
||||||
@ -201,7 +140,7 @@ export function regSelectTopicCommand(context: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
export function regReloadTopicCommand(context: vscode.ExtensionContext) {
|
export function regReloadTopicCommand(context: vscode.ExtensionContext) {
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
vscode.commands.registerCommand('devchat-topicview.reloadTopic', async (item: TopicTreeItem) => {
|
vscode.commands.registerCommand('devchat-topicview.reloadTopic', async () => {
|
||||||
TopicManager.getInstance().loadTopics();
|
TopicManager.getInstance().loadTopics();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -209,7 +148,7 @@ export function regReloadTopicCommand(context: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
export function regApplyDiffResultCommand(context: vscode.ExtensionContext) {
|
export function regApplyDiffResultCommand(context: vscode.ExtensionContext) {
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
vscode.commands.registerCommand('devchat.applyDiffResult', async (data) => {
|
vscode.commands.registerCommand('devchat.applyDiffResult', async () => {
|
||||||
const activeEditor = vscode.window.activeTextEditor;
|
const activeEditor = vscode.window.activeTextEditor;
|
||||||
const fileName = activeEditor!.document.fileName;
|
const fileName = activeEditor!.document.fileName;
|
||||||
|
|
||||||
|
45
src/contributes/commandsBase.ts
Normal file
45
src/contributes/commandsBase.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
import * as childProcess from 'child_process';
|
||||||
|
|
||||||
|
import { UiUtilWrapper } from '../util/uiUtil';
|
||||||
|
|
||||||
|
|
||||||
|
export function checkDevChatDependency(): boolean {
|
||||||
|
try {
|
||||||
|
// Get pipx environment
|
||||||
|
const pipxEnvOutput = childProcess.execSync('python3 -m pipx environment').toString();
|
||||||
|
const binPathRegex = /PIPX_BIN_DIR=\s*(.*)/;
|
||||||
|
|
||||||
|
// Get BIN path from pipx environment
|
||||||
|
const match = pipxEnvOutput.match(binPathRegex);
|
||||||
|
if (match && match[1]) {
|
||||||
|
const binPath = match[1];
|
||||||
|
|
||||||
|
// Add BIN path to PATH
|
||||||
|
process.env.PATH = `${binPath}:${process.env.PATH}`;
|
||||||
|
|
||||||
|
// Check if DevChat is installed
|
||||||
|
childProcess.execSync('devchat --help');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// DevChat dependency check failed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkOpenaiApiKey() {
|
||||||
|
let openaiApiKey = await UiUtilWrapper.secretStorageGet("devchat_OPENAI_API_KEY");
|
||||||
|
if (!openaiApiKey) {
|
||||||
|
openaiApiKey = UiUtilWrapper.getConfiguration('DevChat', 'API_KEY');
|
||||||
|
}
|
||||||
|
if (!openaiApiKey) {
|
||||||
|
openaiApiKey = process.env.OPENAI_API_KEY;
|
||||||
|
}
|
||||||
|
if (!openaiApiKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
208
src/extension.ts
208
src/extension.ts
@ -1,21 +1,18 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
checkOpenaiApiKey,
|
|
||||||
checkDevChatDependency,
|
|
||||||
checkDependencyPackage,
|
|
||||||
registerOpenChatPanelCommand,
|
registerOpenChatPanelCommand,
|
||||||
registerAddContextCommand,
|
registerAddContextCommand,
|
||||||
registerAskForCodeCommand,
|
registerAskForCodeCommand,
|
||||||
registerAskForFileCommand,
|
registerAskForFileCommand,
|
||||||
registerApiKeySettingCommand,
|
registerApiKeySettingCommand,
|
||||||
registerStatusBarItemClickCommand,
|
|
||||||
regTopicDeleteCommand,
|
regTopicDeleteCommand,
|
||||||
regAddTopicCommand,
|
regAddTopicCommand,
|
||||||
regDeleteSelectTopicCommand,
|
regDeleteSelectTopicCommand,
|
||||||
regSelectTopicCommand,
|
regSelectTopicCommand,
|
||||||
regReloadTopicCommand,
|
regReloadTopicCommand,
|
||||||
regApplyDiffResultCommand,
|
regApplyDiffResultCommand,
|
||||||
|
registerStatusBarItemClickCommand,
|
||||||
} from './contributes/commands';
|
} from './contributes/commands';
|
||||||
import { regLanguageContext } from './contributes/context';
|
import { regLanguageContext } from './contributes/context';
|
||||||
import { regDevChatView, regTopicView } from './contributes/views';
|
import { regDevChatView, regTopicView } from './contributes/views';
|
||||||
@ -24,7 +21,7 @@ import ExtensionContextHolder from './util/extensionContext';
|
|||||||
import { logger } from './util/logger';
|
import { logger } from './util/logger';
|
||||||
import { LoggerChannelVscode } from './util/logger_vscode';
|
import { LoggerChannelVscode } from './util/logger_vscode';
|
||||||
import { createStatusBarItem } from './panel/statusBarView';
|
import { createStatusBarItem } from './panel/statusBarView';
|
||||||
import { UiUtilWrapper } from './util/uiUtil';
|
import { UiUtilWrapper, UiUtilVscode } from './util/uiUtil';
|
||||||
|
|
||||||
|
|
||||||
function activate(context: vscode.ExtensionContext) {
|
function activate(context: vscode.ExtensionContext) {
|
||||||
@ -43,200 +40,15 @@ function activate(context: vscode.ExtensionContext) {
|
|||||||
registerAddContextCommand(context);
|
registerAddContextCommand(context);
|
||||||
registerAskForCodeCommand(context);
|
registerAskForCodeCommand(context);
|
||||||
registerAskForFileCommand(context);
|
registerAskForFileCommand(context);
|
||||||
|
registerStatusBarItemClickCommand(context);
|
||||||
|
|
||||||
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
|
createStatusBarItem(context);
|
||||||
|
|
||||||
// Set the status bar item properties
|
regTopicDeleteCommand(context);
|
||||||
// const iconPath = context.asAbsolutePath(path.join('assets', 'tank.png'));
|
regAddTopicCommand(context);
|
||||||
|
regDeleteSelectTopicCommand(context);
|
||||||
// Set the status bar item properties
|
regSelectTopicCommand(context);
|
||||||
statusBarItem.text = `$(warning)DevChat`;
|
regReloadTopicCommand(context);
|
||||||
statusBarItem.tooltip = 'DevChat checking ..., please wait.';
|
regApplyDiffResultCommand(context);
|
||||||
statusBarItem.command = '';
|
|
||||||
|
|
||||||
// add a timer to update the status bar item
|
|
||||||
let devchatStatus = '';
|
|
||||||
let apiKeyStatus = '';
|
|
||||||
let isVersionChangeCompare: boolean|undefined = undefined;
|
|
||||||
setInterval(async () => {
|
|
||||||
const versionOld = await secretStorage.get("DevChatVersionOld");
|
|
||||||
const versionNew = extensionVersion;
|
|
||||||
const versionChanged = versionOld !== versionNew;
|
|
||||||
await secretStorage.store("DevChatVersionOld", versionNew!);
|
|
||||||
|
|
||||||
// status item has three status type
|
|
||||||
// 1. not in a folder
|
|
||||||
// 2. dependence is invalid
|
|
||||||
// 3. ready
|
|
||||||
if (devchatStatus === '' || devchatStatus === 'waiting install devchat') {
|
|
||||||
let bOk = true;
|
|
||||||
let devChat: string | undefined = vscode.workspace.getConfiguration('DevChat').get('DevChatPath');
|
|
||||||
if (!devChat) {
|
|
||||||
bOk = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bOk) {
|
|
||||||
bOk = checkDevChatDependency();
|
|
||||||
}
|
|
||||||
if (bOk && versionChanged && !isVersionChangeCompare) {
|
|
||||||
logger.channel()?.info(`versionOld: ${versionOld}, versionNew: ${versionNew}, versionChanged: ${versionChanged}`);
|
|
||||||
bOk = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bOk) {
|
|
||||||
devchatStatus = 'ready';
|
|
||||||
TopicManager.getInstance().loadTopics();
|
|
||||||
} else {
|
|
||||||
if (devchatStatus === '') {
|
|
||||||
devchatStatus = 'not ready';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (devchatStatus === 'not ready') {
|
|
||||||
// auto install devchat
|
|
||||||
const terminal = vscode.window.createTerminal("DevChat Install");
|
|
||||||
terminal.sendText(`python ${context.extensionUri.fsPath + "/tools/install.py"}`);
|
|
||||||
terminal.show();
|
|
||||||
devchatStatus = 'waiting install devchat';
|
|
||||||
isVersionChangeCompare = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (devchatStatus !== 'ready') {
|
|
||||||
statusBarItem.text = `$(warning)DevChat`;
|
|
||||||
statusBarItem.tooltip = `${devchatStatus}`;
|
|
||||||
statusBarItem.command = undefined;
|
|
||||||
// set statusBarItem warning color
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check api key
|
|
||||||
if (apiKeyStatus === '' || apiKeyStatus === 'please set api key') {
|
|
||||||
const bOk = await checkOpenaiApiKey();
|
|
||||||
if (bOk) {
|
|
||||||
apiKeyStatus = 'ready';
|
|
||||||
} else {
|
|
||||||
apiKeyStatus = 'please set api key';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (apiKeyStatus !== 'ready') {
|
|
||||||
statusBarItem.text = `$(warning)DevChat`;
|
|
||||||
statusBarItem.tooltip = `${apiKeyStatus}`;
|
|
||||||
statusBarItem.command = 'DevChat.OPENAI_API_KEY';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
statusBarItem.text = `$(pass)DevChat`;
|
|
||||||
statusBarItem.tooltip = `ready to chat`;
|
|
||||||
statusBarItem.command = 'devcaht.onStatusBarClick';
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
// Add the status bar item to the status bar
|
|
||||||
statusBarItem.show();
|
|
||||||
context.subscriptions.push(statusBarItem);
|
|
||||||
|
|
||||||
// Register the command
|
|
||||||
context.subscriptions.push(
|
|
||||||
vscode.commands.registerCommand('devcaht.onStatusBarClick', async () => {
|
|
||||||
await vscode.commands.executeCommand('devchat-view.focus');
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
ExtensionContextHolder.provider = new DevChatViewProvider(context);
|
|
||||||
context.subscriptions.push(
|
|
||||||
vscode.window.registerWebviewViewProvider('devchat-view', ExtensionContextHolder.provider, {
|
|
||||||
webviewOptions: { retainContextWhenHidden: true }
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const yourTreeDataProvider = new TopicTreeDataProvider();
|
|
||||||
const yourTreeView = vscode.window.createTreeView('devchat-topicview', {
|
|
||||||
treeDataProvider: yourTreeDataProvider,
|
|
||||||
});
|
|
||||||
context.subscriptions.push(yourTreeView);
|
|
||||||
|
|
||||||
const topicDeleteCallback = async (item: TopicTreeItem) => {
|
|
||||||
const confirm = 'Delete';
|
|
||||||
const cancel = 'Cancel';
|
|
||||||
const label = typeof item.label === 'string' ? item.label : item.label!.label;
|
|
||||||
const truncatedLabel = label.substring(0, 20) + (label.length > 20 ? '...' : '');
|
|
||||||
const result = await vscode.window.showWarningMessage(
|
|
||||||
`Are you sure you want to delete the topic "${truncatedLabel}"?`,
|
|
||||||
{ modal: true },
|
|
||||||
confirm,
|
|
||||||
cancel
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result === confirm) {
|
|
||||||
TopicManager.getInstance().deleteTopic(item.id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
vscode.commands.registerCommand('devchat-topicview.deleteTopic', topicDeleteCallback);
|
|
||||||
|
|
||||||
context.subscriptions.push(
|
|
||||||
vscode.languages.registerCodeActionsProvider(
|
|
||||||
{ pattern: '**', scheme: 'file' },
|
|
||||||
{
|
|
||||||
provideCodeActions: (document, range, context, token) => {
|
|
||||||
const deleteAction = new vscode.CodeAction('Delete Item', vscode.CodeActionKind.QuickFix);
|
|
||||||
deleteAction.command = {
|
|
||||||
title: 'Delete Item',
|
|
||||||
command: 'devchat-topicview.deleteTopic',
|
|
||||||
arguments: [context.diagnostics[0].code],
|
|
||||||
};
|
|
||||||
return [deleteAction];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
vscode.commands.registerCommand('devchat-topicview.addTopic', () => {
|
|
||||||
const topic = TopicManager.getInstance().createTopic();
|
|
||||||
TopicManager.getInstance().setCurrentTopic(topic.topicId);
|
|
||||||
});
|
|
||||||
|
|
||||||
vscode.commands.registerCommand('devchat-topicview.deleteSelectedTopic', () => {
|
|
||||||
const selectedItem = yourTreeDataProvider.selectedItem;
|
|
||||||
if (selectedItem) {
|
|
||||||
topicDeleteCallback(selectedItem);
|
|
||||||
} else {
|
|
||||||
vscode.window.showErrorMessage('No item selected');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
vscode.commands.registerCommand('devchat-topicview.selectTopic', (item: TopicTreeItem) => {
|
|
||||||
yourTreeDataProvider.setSelectedItem(item);
|
|
||||||
TopicManager.getInstance().setCurrentTopic(item.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
vscode.commands.registerCommand('devchat-topicview.reloadTopic', async (item: TopicTreeItem) => {
|
|
||||||
TopicManager.getInstance().loadTopics();
|
|
||||||
});
|
|
||||||
|
|
||||||
context.subscriptions.push(
|
|
||||||
vscode.commands.registerCommand('devchat.applyDiffResult', async (data) => {
|
|
||||||
const activeEditor = vscode.window.activeTextEditor;
|
|
||||||
const fileName = activeEditor!.document.fileName;
|
|
||||||
|
|
||||||
const [leftUri, rightUri] = FilePairManager.getInstance().findPair(fileName) || [undefined, undefined];
|
|
||||||
if (leftUri && rightUri) {
|
|
||||||
// 获取对比的两个文件
|
|
||||||
const leftDoc = await vscode.workspace.openTextDocument(leftUri);
|
|
||||||
const rightDoc = await vscode.workspace.openTextDocument(rightUri);
|
|
||||||
|
|
||||||
// 将右边文档的内容替换到左边文档
|
|
||||||
const leftEditor = await vscode.window.showTextDocument(leftDoc);
|
|
||||||
await leftEditor.edit(editBuilder => {
|
|
||||||
const fullRange = new vscode.Range(0, 0, leftDoc.lineCount, 0);
|
|
||||||
editBuilder.replace(fullRange, rightDoc.getText());
|
|
||||||
});
|
|
||||||
|
|
||||||
// 保存左边文档
|
|
||||||
await leftDoc.save();
|
|
||||||
} else {
|
|
||||||
vscode.window.showErrorMessage('No file to apply diff result.');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
exports.activate = activate;
|
exports.activate = activate;
|
||||||
|
@ -1,141 +1,19 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import DevChat, { LogOptions, LogEntry } from '../toolwrapper/devchat';
|
|
||||||
import { MessageHandler } from './messageHandler';
|
import { MessageHandler } from './messageHandler';
|
||||||
import messageHistory from '../util/messageHistory';
|
|
||||||
import { regInMessage, regOutMessage } from '../util/reg_messages';
|
import { regInMessage, regOutMessage } from '../util/reg_messages';
|
||||||
import { checkOpenaiApiKey } from '../contributes/commands';
|
import { historyMessagesBase, onApiKeyBase } from './historyMessagesBase';
|
||||||
import ExtensionContextHolder from '../util/extensionContext';
|
|
||||||
import { TopicManager } from '../topic/topicManager';
|
|
||||||
|
|
||||||
|
|
||||||
let isApiSet: boolean | undefined = undefined;
|
|
||||||
|
|
||||||
interface LoadHistoryMessages {
|
|
||||||
command: string;
|
|
||||||
entries: Array<LogEntry>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function welcomeMessage(): LogEntry {
|
|
||||||
// create default logEntry to show welcome message
|
|
||||||
return {
|
|
||||||
hash: 'message',
|
|
||||||
parent: '',
|
|
||||||
user: 'system',
|
|
||||||
date: '',
|
|
||||||
request: 'How do I use DevChat?',
|
|
||||||
response: `
|
|
||||||
Do you want to write some code or have a question about the project? Simply right-click on your chosen files or code snippets and add them to DevChat. Feel free to ask me anything or let me help you with coding.
|
|
||||||
|
|
||||||
Don't forget to check out the "+" button on the left of the input to add more context. To see a list of workflows you can run in the context, just type "/". Happy prompting!
|
|
||||||
`,
|
|
||||||
context: []
|
|
||||||
} as LogEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
function apiKeyMissedMessage(): LogEntry {
|
|
||||||
// create default logEntry to show welcome message
|
|
||||||
return {
|
|
||||||
hash: 'message',
|
|
||||||
parent: '',
|
|
||||||
user: 'system',
|
|
||||||
date: '',
|
|
||||||
request: 'Is OPENAI_API_KEY ready?',
|
|
||||||
response: `
|
|
||||||
OPENAI_API_KEY is missing from your environment or settings. Kindly input your OpenAI or DevChat key, and I'll ensure DevChat is all set for you.
|
|
||||||
`,
|
|
||||||
context: []
|
|
||||||
} as LogEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
regInMessage({command: 'historyMessages', options: { skip: 0, maxCount: 0 }});
|
regInMessage({command: 'historyMessages', options: { skip: 0, maxCount: 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 historyMessages(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
export async function historyMessages(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
||||||
const topicId = TopicManager.getInstance().currentTopicId;
|
const historyMessage = historyMessagesBase();
|
||||||
let logEntriesFlat: Array<LogEntry> = [];
|
MessageHandler.sendMessage(panel, historyMessage);
|
||||||
if (topicId) {
|
|
||||||
logEntriesFlat = await TopicManager.getInstance().getTopicHistory(topicId);
|
|
||||||
}
|
|
||||||
messageHistory.clear();
|
|
||||||
|
|
||||||
// TODO handle context
|
|
||||||
const logEntriesFlatFiltered = logEntriesFlat.map((entry) => {
|
|
||||||
return {
|
|
||||||
date: entry.date,
|
|
||||||
hash: entry.hash,
|
|
||||||
request: entry.request,
|
|
||||||
text: entry.response,
|
|
||||||
user: entry.user,
|
|
||||||
parentHash: '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < logEntriesFlat.length; i++) {
|
|
||||||
let entryOld = logEntriesFlat[i];
|
|
||||||
let entryNew = {
|
|
||||||
date: entryOld.date,
|
|
||||||
hash: entryOld.hash,
|
|
||||||
request: entryOld.request,
|
|
||||||
text: entryOld.response,
|
|
||||||
user: entryOld.user,
|
|
||||||
parentHash: '',
|
|
||||||
};
|
|
||||||
if (i > 0) {
|
|
||||||
entryNew.parentHash = logEntriesFlat[i - 1].hash;
|
|
||||||
}
|
|
||||||
messageHistory.add(entryNew);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isApiKeyReady = await checkOpenaiApiKey();
|
|
||||||
isApiSet = true;
|
|
||||||
if (!isApiKeyReady) {
|
|
||||||
const startMessage = [ apiKeyMissedMessage() ];
|
|
||||||
isApiSet = false;
|
|
||||||
|
|
||||||
MessageHandler.sendMessage(panel, {
|
|
||||||
command: 'loadHistoryMessages',
|
|
||||||
entries: startMessage,
|
|
||||||
} as LoadHistoryMessages);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadHistoryMessages: LoadHistoryMessages = {
|
|
||||||
command: 'loadHistoryMessages',
|
|
||||||
entries: logEntriesFlat.length>0? logEntriesFlat : [welcomeMessage()],
|
|
||||||
};
|
|
||||||
|
|
||||||
MessageHandler.sendMessage(panel, loadHistoryMessages);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function isValidApiKey(apiKey: string) {
|
|
||||||
let apiKeyStrim = apiKey.trim();
|
|
||||||
if (apiKeyStrim.indexOf('sk-') !== 0 && apiKeyStrim.indexOf('DC.') !== 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isWaitForApiKey() {
|
|
||||||
if (isApiSet === undefined) {
|
|
||||||
isApiSet = await checkOpenaiApiKey();
|
|
||||||
}
|
|
||||||
return !isApiSet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function onApiKey(apiKey: string, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
export async function onApiKey(apiKey: string, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
||||||
if (!isValidApiKey(apiKey)) {
|
const resMessage = await onApiKeyBase(apiKey);
|
||||||
MessageHandler.sendMessage(panel, { command: 'receiveMessage', text: 'Your API key is invalid. We support OpenAI and DevChat keys. Please reset the key.', hash: '', user: 'system', date: '', isError: false });
|
MessageHandler.sendMessage(panel, resMessage);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isApiSet = true;
|
|
||||||
|
|
||||||
const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context?.secrets!;
|
|
||||||
secretStorage.store("devchat_OPENAI_API_KEY", apiKey);
|
|
||||||
|
|
||||||
const welcomeMessageText = welcomeMessage().response;
|
|
||||||
MessageHandler.sendMessage(panel, { command: 'receiveMessage', text: `Your OPENAI_API_KEY is set. Enjoy DevChat!\n${welcomeMessageText}`, hash: '', user: 'system', date: '', isError: false });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
134
src/handler/historyMessagesBase.ts
Normal file
134
src/handler/historyMessagesBase.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import { checkOpenaiApiKey } from '../contributes/commandsBase';
|
||||||
|
import { TopicManager } from '../topic/topicManager';
|
||||||
|
import { LogEntry } from '../toolwrapper/devchat';
|
||||||
|
import messageHistory from '../util/messageHistory';
|
||||||
|
import { UiUtilWrapper } from '../util/uiUtil';
|
||||||
|
|
||||||
|
let isApiSet: boolean | undefined = undefined;
|
||||||
|
|
||||||
|
interface LoadHistoryMessages {
|
||||||
|
command: string;
|
||||||
|
entries: Array<LogEntry>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function welcomeMessage(): LogEntry {
|
||||||
|
// create default logEntry to show welcome message
|
||||||
|
return {
|
||||||
|
hash: 'message',
|
||||||
|
parent: '',
|
||||||
|
user: 'system',
|
||||||
|
date: '',
|
||||||
|
request: 'How do I use DevChat?',
|
||||||
|
response: `
|
||||||
|
Do you want to write some code or have a question about the project? Simply right-click on your chosen files or code snippets and add them to DevChat. Feel free to ask me anything or let me help you with coding.
|
||||||
|
|
||||||
|
Don't forget to check out the "+" button on the left of the input to add more context. To see a list of workflows you can run in the context, just type "/". Happy prompting!
|
||||||
|
`,
|
||||||
|
context: []
|
||||||
|
} as LogEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
function apiKeyMissedMessage(): LogEntry {
|
||||||
|
// create default logEntry to show welcome message
|
||||||
|
return {
|
||||||
|
hash: 'message',
|
||||||
|
parent: '',
|
||||||
|
user: 'system',
|
||||||
|
date: '',
|
||||||
|
request: 'Is OPENAI_API_KEY ready?',
|
||||||
|
response: `
|
||||||
|
OPENAI_API_KEY is missing from your environment or settings. Kindly input your OpenAI or DevChat key, and I'll ensure DevChat is all set for you.
|
||||||
|
`,
|
||||||
|
context: []
|
||||||
|
} as LogEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidApiKey(apiKey: string) {
|
||||||
|
let apiKeyStrim = apiKey.trim();
|
||||||
|
if (apiKeyStrim.indexOf('sk-') !== 0 && apiKeyStrim.indexOf('DC.') !== 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isWaitForApiKey() {
|
||||||
|
if (isApiSet === undefined) {
|
||||||
|
isApiSet = await checkOpenaiApiKey();
|
||||||
|
}
|
||||||
|
return !isApiSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadTopicHistoryLogs() : Promise<Array<LogEntry>> {
|
||||||
|
const topicId = TopicManager.getInstance().currentTopicId;
|
||||||
|
let logEntriesFlat: Array<LogEntry> = [];
|
||||||
|
if (topicId) {
|
||||||
|
logEntriesFlat = await TopicManager.getInstance().getTopicHistory(topicId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logEntriesFlat;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateCurrentMessageHistory(logEntries: Array<LogEntry>): void {
|
||||||
|
messageHistory.clear();
|
||||||
|
|
||||||
|
for (let i = 0; i < logEntries.length; i++) {
|
||||||
|
let entryOld = logEntries[i];
|
||||||
|
let entryNew = {
|
||||||
|
date: entryOld.date,
|
||||||
|
hash: entryOld.hash,
|
||||||
|
request: entryOld.request,
|
||||||
|
text: entryOld.response,
|
||||||
|
user: entryOld.user,
|
||||||
|
parentHash: '',
|
||||||
|
};
|
||||||
|
if (i > 0) {
|
||||||
|
entryNew.parentHash = logEntries[i - 1].hash;
|
||||||
|
}
|
||||||
|
messageHistory.add(entryNew);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiKeyInvalidMessage(): Promise<LoadHistoryMessages|undefined> {
|
||||||
|
const isApiKeyReady = await checkOpenaiApiKey();
|
||||||
|
isApiSet = true;
|
||||||
|
if (!isApiKeyReady) {
|
||||||
|
const startMessage = [ apiKeyMissedMessage() ];
|
||||||
|
isApiSet = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
command: 'loadHistoryMessages',
|
||||||
|
entries: startMessage,
|
||||||
|
} as LoadHistoryMessages;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function historyMessagesBase(): Promise<LoadHistoryMessages> {
|
||||||
|
const logEntriesFlat = await loadTopicHistoryLogs();
|
||||||
|
updateCurrentMessageHistory(logEntriesFlat);
|
||||||
|
|
||||||
|
const apiKeyMessage = await apiKeyInvalidMessage();
|
||||||
|
if (apiKeyMessage) {
|
||||||
|
return apiKeyMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
command: 'loadHistoryMessages',
|
||||||
|
entries: logEntriesFlat.length>0? logEntriesFlat : [welcomeMessage()],
|
||||||
|
} as LoadHistoryMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function onApiKeyBase(apiKey: string): Promise<{command: string, text: string, hash: string, user: string, date: string, isError: boolean}> {
|
||||||
|
if (!isValidApiKey(apiKey)) {
|
||||||
|
return { command: 'receiveMessage', text: 'Your API key is invalid. We support OpenAI and DevChat keys. Please reset the key.', hash: '', user: 'system', date: '', isError: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
isApiSet = true;
|
||||||
|
UiUtilWrapper.storeSecret("devchat_OPENAI_API_KEY", apiKey);
|
||||||
|
|
||||||
|
const welcomeMessageText = welcomeMessage().response;
|
||||||
|
return { command: 'receiveMessage', text: `Your OPENAI_API_KEY is set. Enjoy DevChat!\n${welcomeMessageText}`, hash: '', user: 'system', date: '', isError: false };
|
||||||
|
}
|
@ -5,9 +5,9 @@ import * as vscode from 'vscode';
|
|||||||
import '../command/loadCommands';
|
import '../command/loadCommands';
|
||||||
import '../context/loadContexts';
|
import '../context/loadContexts';
|
||||||
import { logger } from '../util/logger';
|
import { logger } from '../util/logger';
|
||||||
import { on } from 'events';
|
import { isWaitForApiKey } from './historyMessagesBase';
|
||||||
import { isWaitForApiKey, onApiKey } from './historyMessages';
|
import { onApiKey } from './historyMessages';
|
||||||
import { checkOpenaiApiKey } from '../contributes/commands';
|
import { checkOpenaiApiKey } from '../contributes/commandsBase';
|
||||||
|
|
||||||
|
|
||||||
export class MessageHandler {
|
export class MessageHandler {
|
||||||
@ -20,6 +20,8 @@ export class MessageHandler {
|
|||||||
this.handlers[command] = handler;
|
this.handlers[command] = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
let isNeedSendResponse = false;
|
||||||
if (message.command === 'sendMessage') {
|
if (message.command === 'sendMessage') {
|
||||||
|
@ -1,76 +1,12 @@
|
|||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import DevChat, { ChatResponse } from '../toolwrapper/devchat';
|
|
||||||
import CommandManager from '../command/commandManager';
|
|
||||||
import { logger } from '../util/logger';
|
|
||||||
import { MessageHandler } from './messageHandler';
|
import { MessageHandler } from './messageHandler';
|
||||||
import messageHistory from '../util/messageHistory';
|
|
||||||
import CustomCommands from '../command/customCommand';
|
|
||||||
import { regInMessage, regOutMessage } from '../util/reg_messages';
|
import { regInMessage, regOutMessage } from '../util/reg_messages';
|
||||||
import { TopicManager } from '../topic/topicManager';
|
import { stopDevChatBase, sendMessageBase } from './sendMessageBase';
|
||||||
|
|
||||||
|
|
||||||
let _lastMessage: any = undefined;
|
let _lastMessage: any = undefined;
|
||||||
|
|
||||||
|
|
||||||
// Add this function to messageHandler.ts
|
|
||||||
function parseMessage(message: string): { context: string[]; instruction: string[]; reference: string[]; text: string } {
|
|
||||||
const contextRegex = /\[context\|(.*?)\]/g;
|
|
||||||
const instructionRegex = /\[instruction\|(.*?)\]/g;
|
|
||||||
const referenceRegex = /\[reference\|(.*?)\]/g;
|
|
||||||
|
|
||||||
const contextPaths = [];
|
|
||||||
const instructionPaths = [];
|
|
||||||
const referencePaths = [];
|
|
||||||
|
|
||||||
let match;
|
|
||||||
|
|
||||||
// 提取 context
|
|
||||||
while ((match = contextRegex.exec(message)) !== null) {
|
|
||||||
contextPaths.push(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取 instruction
|
|
||||||
while ((match = instructionRegex.exec(message)) !== null) {
|
|
||||||
instructionPaths.push(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取 reference
|
|
||||||
while ((match = referenceRegex.exec(message)) !== null) {
|
|
||||||
referencePaths.push(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除标签,保留纯文本
|
|
||||||
const text = message
|
|
||||||
.replace(contextRegex, '')
|
|
||||||
.replace(instructionRegex, '')
|
|
||||||
.replace(referenceRegex, '')
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
return { context: contextPaths, instruction: instructionPaths, reference: referencePaths, text };
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInstructionFiles(): string[] {
|
|
||||||
const instructionFiles: string[] = [];
|
|
||||||
|
|
||||||
const customCommands = CustomCommands.getInstance().getCommands();
|
|
||||||
// visit customCommands, get default command
|
|
||||||
for (const command of customCommands) {
|
|
||||||
if (command.default) {
|
|
||||||
for (const instruction of command.instructions) {
|
|
||||||
instructionFiles.push(`./.chat/workflows/${command.name}/${instruction}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return instructionFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
const devChat = new DevChat();
|
|
||||||
let userStop = false;
|
|
||||||
|
|
||||||
|
|
||||||
regInMessage({command: 'sendMessage', text: '', hash: undefined});
|
regInMessage({command: 'sendMessage', text: '', hash: undefined});
|
||||||
regOutMessage({ command: 'receiveMessage', text: 'xxxx', hash: 'xxx', user: 'xxx', date: 'xxx'});
|
regOutMessage({ command: 'receiveMessage', text: 'xxxx', hash: 'xxx', user: 'xxx', date: 'xxx'});
|
||||||
regOutMessage({ command: 'receiveMessagePartial', text: 'xxxx', user: 'xxx', date: 'xxx'});
|
regOutMessage({ command: 'receiveMessagePartial', text: 'xxxx', user: 'xxx', date: 'xxx'});
|
||||||
@ -81,72 +17,12 @@ regOutMessage({ command: 'receiveMessagePartial', text: 'xxxx', user: 'xxx', dat
|
|||||||
export async function sendMessage(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
export async function sendMessage(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
||||||
_lastMessage = message;
|
_lastMessage = message;
|
||||||
|
|
||||||
const newText2 = await CommandManager.getInstance().processText(message.text);
|
const responseMessage = await sendMessageBase(message, (data: { command: string, text: string, user: string, date: string}) => {
|
||||||
const parsedMessage = parseMessage(newText2);
|
MessageHandler.sendMessage(panel, data);
|
||||||
const chatOptions: any = {};
|
});
|
||||||
|
if (responseMessage) {
|
||||||
let parentHash = undefined;
|
MessageHandler.sendMessage(panel, responseMessage);
|
||||||
logger.channel()?.info(`request message hash: ${message.hash}`);
|
|
||||||
if (message.hash) {
|
|
||||||
const hmessage = messageHistory.find(message.hash);
|
|
||||||
parentHash = hmessage ? message.parentHash : undefined;
|
|
||||||
} else {
|
|
||||||
const hmessage = messageHistory.findLast();
|
|
||||||
parentHash = hmessage ? hmessage.hash : undefined;
|
|
||||||
}
|
}
|
||||||
if (parentHash) {
|
|
||||||
chatOptions.parent = parentHash;
|
|
||||||
}
|
|
||||||
logger.channel()?.info(`parent hash: ${parentHash}`);
|
|
||||||
|
|
||||||
if (parsedMessage.context.length > 0) {
|
|
||||||
chatOptions.context = parsedMessage.context;
|
|
||||||
}
|
|
||||||
|
|
||||||
chatOptions.header = getInstructionFiles();
|
|
||||||
if (parsedMessage.instruction.length > 0) {
|
|
||||||
chatOptions.header = parsedMessage.instruction;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsedMessage.reference.length > 0) {
|
|
||||||
chatOptions.reference = parsedMessage.reference;
|
|
||||||
}
|
|
||||||
|
|
||||||
let partialDataText = '';
|
|
||||||
const onData = (partialResponse: ChatResponse) => {
|
|
||||||
const responseText = partialResponse.response.replace(/```\ncommitmsg/g, "```commitmsg");
|
|
||||||
partialDataText = responseText;
|
|
||||||
MessageHandler.sendMessage(panel, { command: 'receiveMessagePartial', text: responseText, user: partialResponse.user, date: partialResponse.date }, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const chatResponse = await devChat.chat(parsedMessage.text, chatOptions, onData);
|
|
||||||
|
|
||||||
if (!chatResponse.isError) {
|
|
||||||
messageHistory.add({request: message.text, text: chatResponse.response, parentHash, hash: chatResponse['prompt-hash'], user: chatResponse.user, date: chatResponse.date });
|
|
||||||
|
|
||||||
let topicId = TopicManager.getInstance().currentTopicId;
|
|
||||||
if (!topicId) {
|
|
||||||
// create new topic
|
|
||||||
const topic = TopicManager.getInstance().createTopic();
|
|
||||||
topicId = topic.topicId;
|
|
||||||
}
|
|
||||||
|
|
||||||
TopicManager.getInstance().updateTopic(topicId!, chatResponse['prompt-hash'], Number(chatResponse.date), message.text, chatResponse.response);
|
|
||||||
}
|
|
||||||
|
|
||||||
let responseText = chatResponse.response.replace(/```\ncommitmsg/g, "```commitmsg");
|
|
||||||
if (userStop) {
|
|
||||||
userStop = false;
|
|
||||||
if (responseText.indexOf('Exit code: undefined') >= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (chatResponse.isError) {
|
|
||||||
responseText = partialDataText + responseText;
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageHandler.sendMessage(panel, { command: 'receiveMessage', text: responseText, hash: chatResponse['prompt-hash'], user: chatResponse.user, date: chatResponse.date, isError: chatResponse.isError });
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// regeneration last message again
|
// regeneration last message again
|
||||||
@ -160,9 +36,7 @@ export async function regeneration(message: any, panel: vscode.WebviewPanel|vsco
|
|||||||
|
|
||||||
regInMessage({command: 'stopDevChat'});
|
regInMessage({command: 'stopDevChat'});
|
||||||
export async function stopDevChat(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
export async function stopDevChat(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
|
||||||
logger.channel()?.info(`Stopping devchat`);
|
stopDevChatBase(message);
|
||||||
userStop = true;
|
|
||||||
devChat.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
160
src/handler/sendMessageBase.ts
Normal file
160
src/handler/sendMessageBase.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import DevChat, { ChatResponse } from '../toolwrapper/devchat';
|
||||||
|
import CommandManager from '../command/commandManager';
|
||||||
|
import { logger } from '../util/logger';
|
||||||
|
import messageHistory from '../util/messageHistory';
|
||||||
|
import { TopicManager } from '../topic/topicManager';
|
||||||
|
import CustomCommands from '../command/customCommand';
|
||||||
|
|
||||||
|
|
||||||
|
// Add this function to messageHandler.ts
|
||||||
|
function parseMessage(message: string): { context: string[]; instruction: string[]; reference: string[]; text: string } {
|
||||||
|
const contextRegex = /\[context\|(.*?)\]/g;
|
||||||
|
const instructionRegex = /\[instruction\|(.*?)\]/g;
|
||||||
|
const referenceRegex = /\[reference\|(.*?)\]/g;
|
||||||
|
|
||||||
|
const contextPaths = [];
|
||||||
|
const instructionPaths = [];
|
||||||
|
const referencePaths = [];
|
||||||
|
|
||||||
|
let match;
|
||||||
|
|
||||||
|
// 提取 context
|
||||||
|
while ((match = contextRegex.exec(message)) !== null) {
|
||||||
|
contextPaths.push(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取 instruction
|
||||||
|
while ((match = instructionRegex.exec(message)) !== null) {
|
||||||
|
instructionPaths.push(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取 reference
|
||||||
|
while ((match = referenceRegex.exec(message)) !== null) {
|
||||||
|
referencePaths.push(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除标签,保留纯文本
|
||||||
|
const text = message
|
||||||
|
.replace(contextRegex, '')
|
||||||
|
.replace(instructionRegex, '')
|
||||||
|
.replace(referenceRegex, '')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
return { context: contextPaths, instruction: instructionPaths, reference: referencePaths, text };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInstructionFiles(): string[] {
|
||||||
|
const instructionFiles: string[] = [];
|
||||||
|
|
||||||
|
const customCommands = CustomCommands.getInstance().getCommands();
|
||||||
|
// visit customCommands, get default command
|
||||||
|
for (const command of customCommands) {
|
||||||
|
if (command.default) {
|
||||||
|
for (const instruction of command.instructions) {
|
||||||
|
instructionFiles.push(`./.chat/workflows/${command.name}/${instruction}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instructionFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
const devChat = new DevChat();
|
||||||
|
let userStop = false;
|
||||||
|
|
||||||
|
|
||||||
|
// 将解析消息的部分提取到一个单独的函数中
|
||||||
|
async function parseMessageAndSetOptions(message: any, chatOptions: any): Promise<{ context: string[]; instruction: string[]; reference: string[]; text: string }> {
|
||||||
|
const newText2 = await CommandManager.getInstance().processText(message.text);
|
||||||
|
const parsedMessage = parseMessage(newText2);
|
||||||
|
|
||||||
|
if (parsedMessage.context.length > 0) {
|
||||||
|
chatOptions.context = parsedMessage.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
chatOptions.header = getInstructionFiles();
|
||||||
|
if (parsedMessage.instruction.length > 0) {
|
||||||
|
chatOptions.header = parsedMessage.instruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedMessage.reference.length > 0) {
|
||||||
|
chatOptions.reference = parsedMessage.reference;
|
||||||
|
}
|
||||||
|
return parsedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将处理父哈希的部分提取到一个单独的函数中
|
||||||
|
function getParentHash(message: any) {
|
||||||
|
let parentHash = undefined;
|
||||||
|
logger.channel()?.info(`request message hash: ${message.hash}`);
|
||||||
|
if (message.hash) {
|
||||||
|
const hmessage = messageHistory.find(message.hash);
|
||||||
|
parentHash = hmessage ? message.parentHash : undefined;
|
||||||
|
} else {
|
||||||
|
const hmessage = messageHistory.findLast();
|
||||||
|
parentHash = hmessage ? hmessage.hash : undefined;
|
||||||
|
}
|
||||||
|
logger.channel()?.info(`parent hash: ${parentHash}`);
|
||||||
|
return parentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleTopic(parentHash:string, message: any, chatResponse: ChatResponse) {
|
||||||
|
if (!chatResponse.isError) {
|
||||||
|
messageHistory.add({ request: message.text, text: chatResponse.response, parentHash, hash: chatResponse['prompt-hash'], user: chatResponse.user, date: chatResponse.date });
|
||||||
|
|
||||||
|
let topicId = TopicManager.getInstance().currentTopicId;
|
||||||
|
if (!topicId) {
|
||||||
|
// create new topic
|
||||||
|
const topic = TopicManager.getInstance().createTopic();
|
||||||
|
topicId = topic.topicId;
|
||||||
|
}
|
||||||
|
|
||||||
|
TopicManager.getInstance().updateTopic(topicId!, chatResponse['prompt-hash'], Number(chatResponse.date), message.text, chatResponse.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handlerResponseText(partialDataText: string, chatResponse: ChatResponse) : Promise<string|undefined> {
|
||||||
|
let responseText = chatResponse.response.replace(/```\ncommitmsg/g, "```commitmsg");
|
||||||
|
if (userStop) {
|
||||||
|
userStop = false;
|
||||||
|
if (responseText.indexOf('Exit code: undefined') >= 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chatResponse.isError) {
|
||||||
|
responseText = partialDataText + responseText;
|
||||||
|
}
|
||||||
|
return responseText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重构后的sendMessage函数
|
||||||
|
export async function sendMessageBase(message: any, handlePartialData: (data: { command: string, text: string, user: string, date: string}) => void): Promise<{ command: string, text: string, hash: string, user: string, date: string, isError: boolean }|undefined> {
|
||||||
|
const chatOptions: any = {};
|
||||||
|
const parsedMessage = await parseMessageAndSetOptions(message, chatOptions);
|
||||||
|
|
||||||
|
const parentHash = getParentHash(message);
|
||||||
|
if (parentHash) {
|
||||||
|
chatOptions.parent = parentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
let partialDataText = '';
|
||||||
|
const onData = (partialResponse: ChatResponse) => {
|
||||||
|
partialDataText = partialResponse.response.replace(/```\ncommitmsg/g, "```commitmsg");
|
||||||
|
handlePartialData({ command: 'receiveMessagePartial', text: partialDataText!, user: partialResponse.user, date: partialResponse.date });
|
||||||
|
};
|
||||||
|
|
||||||
|
const chatResponse = await devChat.chat(parsedMessage.text, chatOptions, onData);
|
||||||
|
await handleTopic(parentHash!, message, chatResponse);
|
||||||
|
const responseText = await handlerResponseText(partialDataText, chatResponse);
|
||||||
|
if (responseText === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { command: 'receiveMessage', text: responseText, hash: chatResponse['prompt-hash'], user: chatResponse.user, date: chatResponse.date, isError: chatResponse.isError };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stopDevChatBase(message: any): Promise<void> {
|
||||||
|
logger.channel()?.info(`Stopping devchat`);
|
||||||
|
userStop = true;
|
||||||
|
devChat.stop();
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
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 * as ncp from 'ncp';
|
|
||||||
|
|
||||||
import { logger } from '../util/logger';
|
import { logger } from '../util/logger';
|
||||||
|
import { UiUtilWrapper } from '../util/uiUtil';
|
||||||
|
|
||||||
|
|
||||||
function copyFileSync(source: string, target: string) {
|
function copyFileSync(source: string, target: string) {
|
||||||
@ -32,13 +32,11 @@ function copyFileSync(source: string, target: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createChatDirectoryAndCopyInstructionsSync(extensionUri: vscode.Uri) {
|
export function createChatDirectoryAndCopyInstructionsSync(extensionUri: vscode.Uri) {
|
||||||
|
const workspaceRoot = UiUtilWrapper.workspaceFoldersFirstPath();
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
if (!workspaceRoot) {
|
||||||
if (!workspaceFolders) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspaceRoot = workspaceFolders[0].uri.fsPath;
|
|
||||||
const chatWorkflowsDirPath = path.join(workspaceRoot, '.chat', 'workflows');
|
const chatWorkflowsDirPath = path.join(workspaceRoot, '.chat', 'workflows');
|
||||||
const instructionsSrcPath = path.join(extensionUri.fsPath, 'workflows');
|
const instructionsSrcPath = path.join(extensionUri.fsPath, 'workflows');
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import WebviewManager from './webviewManager';
|
|||||||
import CustomCommands from '../command/customCommand';
|
import CustomCommands from '../command/customCommand';
|
||||||
import CommandManager from '../command/commandManager';
|
import CommandManager from '../command/commandManager';
|
||||||
import { createChatDirectoryAndCopyInstructionsSync } from '../init/chatConfig';
|
import { createChatDirectoryAndCopyInstructionsSync } from '../init/chatConfig';
|
||||||
|
import { UiUtilWrapper } from '../util/uiUtil';
|
||||||
|
|
||||||
export default class ChatPanel {
|
export default class ChatPanel {
|
||||||
private static _instance: ChatPanel | undefined;
|
private static _instance: ChatPanel | undefined;
|
||||||
|
@ -8,6 +8,7 @@ import { createChatDirectoryAndCopyInstructionsSync } from '../init/chatConfig';
|
|||||||
import ExtensionContextHolder from '../util/extensionContext';
|
import ExtensionContextHolder from '../util/extensionContext';
|
||||||
import CustomCommands from '../command/customCommand';
|
import CustomCommands from '../command/customCommand';
|
||||||
import { TopicManager } from '../topic/topicManager';
|
import { TopicManager } from '../topic/topicManager';
|
||||||
|
import { UiUtilWrapper } from '../util/uiUtil';
|
||||||
|
|
||||||
|
|
||||||
export class DevChatViewProvider implements vscode.WebviewViewProvider {
|
export class DevChatViewProvider implements vscode.WebviewViewProvider {
|
||||||
|
@ -1,20 +1,8 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as path from 'path';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
import { checkOpenAiAPIKey, checkDevChatDependency } from '../contributes/commands';
|
import { dependencyCheck } from './statusBarViewBase';
|
||||||
import { logger } from '../util/logger';
|
|
||||||
import { TopicManager } from '../topic/topicManager';
|
|
||||||
|
|
||||||
|
|
||||||
function getExtensionVersion(context: vscode.ExtensionContext): string {
|
|
||||||
const packageJsonPath = path.join(context.extensionUri.fsPath, 'package.json');
|
|
||||||
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
|
||||||
const packageJson = JSON.parse(packageJsonContent);
|
|
||||||
|
|
||||||
return packageJson.version;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createStatusBarItem(context: vscode.ExtensionContext): vscode.StatusBarItem {
|
export function createStatusBarItem(context: vscode.ExtensionContext): vscode.StatusBarItem {
|
||||||
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
|
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
|
||||||
|
|
||||||
@ -24,70 +12,16 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St
|
|||||||
statusBarItem.command = '';
|
statusBarItem.command = '';
|
||||||
|
|
||||||
// add a timer to update the status bar item
|
// add a timer to update the status bar item
|
||||||
let devchatStatus = '';
|
|
||||||
let apiKeyStatus = '';
|
|
||||||
|
|
||||||
const extensionVersion = getExtensionVersion(context);
|
|
||||||
const secretStorage: vscode.SecretStorage = context.secrets;
|
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
const versionOld = await secretStorage.get("devchat_version_old");
|
const [devchatStatus, apiKeyStatus] = await dependencyCheck();
|
||||||
const versionNew = extensionVersion;
|
|
||||||
const versionChanged = versionOld !== versionNew;
|
|
||||||
secretStorage.store("devchat_version_old", versionNew!);
|
|
||||||
|
|
||||||
// status item has three status type
|
|
||||||
// 1. not in a folder
|
|
||||||
// 2. dependence is invalid
|
|
||||||
// 3. ready
|
|
||||||
if (devchatStatus === '' || devchatStatus === 'waiting install devchat') {
|
|
||||||
let bOk = true;
|
|
||||||
let devChat: string | undefined = UiUtilWrapper.getConfiguration('DevChat', 'DevChatPath');
|
|
||||||
if (!devChat) {
|
|
||||||
bOk = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bOk) {
|
|
||||||
bOk = checkDevChatDependency();
|
|
||||||
}
|
|
||||||
if (bOk && versionChanged) {
|
|
||||||
bOk = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bOk) {
|
|
||||||
devchatStatus = 'ready';
|
|
||||||
TopicManager.getInstance().loadTopics();
|
|
||||||
} else {
|
|
||||||
if (devchatStatus === '') {
|
|
||||||
devchatStatus = 'not ready';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (devchatStatus === 'not ready') {
|
|
||||||
// auto install devchat
|
|
||||||
const terminal = vscode.window.createTerminal("DevChat Install");
|
|
||||||
terminal.sendText(`python ${context.extensionUri.fsPath + "/tools/install.py"}`);
|
|
||||||
terminal.show();
|
|
||||||
devchatStatus = 'waiting install devchat';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (devchatStatus !== 'ready') {
|
if (devchatStatus !== 'ready') {
|
||||||
statusBarItem.text = `$(warning)DevChat`;
|
statusBarItem.text = `$(warning)DevChat`;
|
||||||
statusBarItem.tooltip = `${devchatStatus}`;
|
statusBarItem.tooltip = `${devchatStatus}`;
|
||||||
statusBarItem.command = '';
|
statusBarItem.command = undefined;
|
||||||
// set statusBarItem warning color
|
// set statusBarItem warning color
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check api key
|
|
||||||
if (apiKeyStatus === '' || apiKeyStatus === 'please set api key') {
|
|
||||||
const bOk = await checkOpenAiAPIKey();
|
|
||||||
if (bOk) {
|
|
||||||
apiKeyStatus = 'ready';
|
|
||||||
} else {
|
|
||||||
apiKeyStatus = 'please set api key';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (apiKeyStatus !== 'ready') {
|
if (apiKeyStatus !== 'ready') {
|
||||||
statusBarItem.text = `$(warning)DevChat`;
|
statusBarItem.text = `$(warning)DevChat`;
|
||||||
statusBarItem.tooltip = `${apiKeyStatus}`;
|
statusBarItem.tooltip = `${apiKeyStatus}`;
|
||||||
|
81
src/panel/statusBarViewBase.ts
Normal file
81
src/panel/statusBarViewBase.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { logger } from "../util/logger";
|
||||||
|
|
||||||
|
import { UiUtilWrapper } from "../util/uiUtil";
|
||||||
|
import { TopicManager } from "../topic/topicManager";
|
||||||
|
import { checkDevChatDependency } from "../contributes/commandsBase";
|
||||||
|
import { checkOpenaiApiKey } from "../contributes/commandsBase";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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 apiKeyStatus = '';
|
||||||
|
let isVersionChangeCompare: boolean|undefined = undefined;
|
||||||
|
export async function dependencyCheck(): Promise<[string, string]> {
|
||||||
|
let versionChanged = false;
|
||||||
|
if (isVersionChangeCompare === undefined) {
|
||||||
|
const versionOld = await UiUtilWrapper.secretStorageGet("DevChatVersionOld");
|
||||||
|
const versionNew = getExtensionVersion();
|
||||||
|
const versionChanged = versionOld !== versionNew;
|
||||||
|
UiUtilWrapper.storeSecret("DevChatVersionOld", versionNew!);
|
||||||
|
|
||||||
|
isVersionChangeCompare = true;
|
||||||
|
logger.channel()?.info(`versionOld: ${versionOld}, versionNew: ${versionNew}, versionChanged: ${versionChanged}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// status item has three status type
|
||||||
|
// 1. not in a folder
|
||||||
|
// 2. dependence is invalid
|
||||||
|
// 3. ready
|
||||||
|
if (devchatStatus === '' || devchatStatus === 'waiting install devchat') {
|
||||||
|
let bOk = true;
|
||||||
|
let devChat: string | undefined = UiUtilWrapper.getConfiguration('DevChat', 'DevChatPath');
|
||||||
|
if (!devChat) {
|
||||||
|
bOk = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bOk) {
|
||||||
|
bOk = checkDevChatDependency();
|
||||||
|
}
|
||||||
|
if (bOk && versionChanged) {
|
||||||
|
bOk = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bOk) {
|
||||||
|
devchatStatus = 'ready';
|
||||||
|
TopicManager.getInstance().loadTopics();
|
||||||
|
} else {
|
||||||
|
if (devchatStatus === '') {
|
||||||
|
devchatStatus = 'not ready';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (devchatStatus === 'not ready') {
|
||||||
|
// auto install devchat
|
||||||
|
UiUtilWrapper.runTerminal('DevChat Install', `python ${UiUtilWrapper.extensionPath() + "/tools/install.py"}`);
|
||||||
|
devchatStatus = 'waiting install devchat';
|
||||||
|
isVersionChangeCompare = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check api key
|
||||||
|
if (apiKeyStatus === '' || apiKeyStatus === 'please set api key') {
|
||||||
|
const bOk = await checkOpenaiApiKey();
|
||||||
|
if (bOk) {
|
||||||
|
apiKeyStatus = 'ready';
|
||||||
|
} else {
|
||||||
|
apiKeyStatus = 'please set api key';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [devchatStatus, apiKeyStatus];
|
||||||
|
}
|
@ -84,7 +84,7 @@ class DevChat {
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOpenAiApiKey(): Promise<string | undefined> {
|
async getOpenaiApiKey(): Promise<string | undefined> {
|
||||||
let openaiApiKey = await UiUtilWrapper.secretStorageGet("devchat_OPENAI_API_KEY");
|
let openaiApiKey = await UiUtilWrapper.secretStorageGet("devchat_OPENAI_API_KEY");
|
||||||
if (!openaiApiKey) {
|
if (!openaiApiKey) {
|
||||||
openaiApiKey = UiUtilWrapper.getConfiguration('DevChat', 'API_KEY');
|
openaiApiKey = UiUtilWrapper.getConfiguration('DevChat', 'API_KEY');
|
||||||
@ -153,8 +153,9 @@ class DevChat {
|
|||||||
openAiApiBase = "https://xw4ymuy6qj.ap-southeast-1.awsapprunner.com/api/v1";
|
openAiApiBase = "https://xw4ymuy6qj.ap-southeast-1.awsapprunner.com/api/v1";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vscode.workspace.getConfiguration('DevChat').get('API_ENDPOINT')) {
|
|
||||||
openAiApiBase = vscode.workspace.getConfiguration('DevChat').get('API_ENDPOINT');
|
if (UiUtilWrapper.getConfiguration('DevChat', 'API_ENDPOINT')) {
|
||||||
|
openAiApiBase = UiUtilWrapper.getConfiguration('DevChat', 'API_ENDPOINT');
|
||||||
}
|
}
|
||||||
|
|
||||||
const openAiApiBaseObject = openAiApiBase ? { OPENAI_API_BASE: openAiApiBase } : {};
|
const openAiApiBaseObject = openAiApiBase ? { OPENAI_API_BASE: openAiApiBase } : {};
|
||||||
|
@ -4,6 +4,7 @@ import * as fs from 'fs';
|
|||||||
|
|
||||||
import { logger } from "../util/logger";
|
import { logger } from "../util/logger";
|
||||||
import { CommandRun } from "../util/commonUtil";
|
import { CommandRun } from "../util/commonUtil";
|
||||||
|
import { UiUtilWrapper } from "../util/uiUtil";
|
||||||
|
|
||||||
interface DtmResponse {
|
interface DtmResponse {
|
||||||
status: number;
|
status: number;
|
||||||
|
@ -5,6 +5,7 @@ import * as fs from 'fs';
|
|||||||
import DevChat, { LogEntry, LogOptions } from '../toolwrapper/devchat';
|
import DevChat, { LogEntry, LogOptions } from '../toolwrapper/devchat';
|
||||||
|
|
||||||
import { loadTopicList } from './loadTopics';
|
import { loadTopicList } from './loadTopics';
|
||||||
|
import { UiUtilWrapper } from '../util/uiUtil';
|
||||||
|
|
||||||
export class Topic {
|
export class Topic {
|
||||||
name: string | undefined;
|
name: string | undefined;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import ExtensionContextHolder from './extensionContext';
|
||||||
|
|
||||||
export interface UiUtil {
|
export interface UiUtil {
|
||||||
languageId(uri: string): Promise<string>;
|
languageId(uri: string): Promise<string>;
|
||||||
@ -6,6 +7,9 @@ export interface UiUtil {
|
|||||||
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>;
|
||||||
|
storeSecret(key: string, value: string): Promise<void>;
|
||||||
|
extensionPath(): string;
|
||||||
|
runTerminal(terminalName:string, command: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -31,10 +35,19 @@ export class UiUtilVscode implements UiUtil {
|
|||||||
vscode.workspace.fs.writeFile(vscode.Uri.file(uri), Buffer.from(content));
|
vscode.workspace.fs.writeFile(vscode.Uri.file(uri), Buffer.from(content));
|
||||||
}
|
}
|
||||||
public async showInputBox(option: object): Promise<string | undefined> {
|
public async showInputBox(option: object): Promise<string | undefined> {
|
||||||
return vscode.window.showInputBox({
|
return vscode.window.showInputBox(option);
|
||||||
prompt: 'Input your custom command',
|
}
|
||||||
placeHolder: 'for example: ls -l'
|
public async storeSecret(key: string, value: string): Promise<void> {
|
||||||
});
|
const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context!.secrets;
|
||||||
|
await secretStorage.store(key, value);
|
||||||
|
}
|
||||||
|
public extensionPath(): string {
|
||||||
|
return ExtensionContextHolder.context!.extensionUri.fsPath;
|
||||||
|
}
|
||||||
|
public runTerminal(terminalName: string, command: string): void {
|
||||||
|
const terminal = vscode.window.createTerminal(terminalName);
|
||||||
|
terminal.sendText(command);
|
||||||
|
terminal.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,4 +75,14 @@ export class UiUtilWrapper {
|
|||||||
public static async showInputBox(option: object): Promise<string | undefined> {
|
public static async showInputBox(option: object): Promise<string | undefined> {
|
||||||
return this._uiUtil?.showInputBox(option);
|
return this._uiUtil?.showInputBox(option);
|
||||||
}
|
}
|
||||||
|
public static async storeSecret(key: string, value: string): Promise<void> {
|
||||||
|
return this._uiUtil?.storeSecret(key, value);
|
||||||
}
|
}
|
||||||
|
public static extensionPath(): string {
|
||||||
|
return this._uiUtil?.extensionPath()!;
|
||||||
|
}
|
||||||
|
public static runTerminal(terminalName: string, command: string): void {
|
||||||
|
this._uiUtil?.runTerminal(terminalName, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,9 @@
|
|||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
},
|
},
|
||||||
"exclude": ["test"]
|
"exclude": ["test"]
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user