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