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-12-20 12:57:55 +08:00
import * as path from 'path' ;
import * as util from 'util' ;
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 { 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-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-12-12 16:39:08 +08:00
import { chatWithDevChat } from '../handler/chatHandler' ;
2024-02-07 17:37:02 +08:00
import { focusDevChatInput } from '../handler/focusHandler' ;
2023-05-10 19:25:11 +08:00
2023-12-20 12:57:55 +08:00
const readdir = util . promisify ( fs . readdir ) ;
const stat = util . promisify ( fs . stat ) ;
const mkdir = util . promisify ( fs . mkdir ) ;
const copyFile = util . promisify ( fs . copyFile ) ;
async function copyDirectory ( src : string , dest : string ) : Promise < void > {
await mkdir ( dest , { recursive : true } ) ;
const entries = await readdir ( src , { withFileTypes : true } ) ;
for ( let entry of entries ) {
const srcPath = path . join ( src , entry . name ) ;
const destPath = path . join ( dest , entry . name ) ;
if ( entry . isDirectory ( ) ) {
await copyDirectory ( srcPath , destPath ) ;
} else {
await copyFile ( srcPath , destPath ) ;
}
}
}
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' ) ;
2024-02-07 17:37:02 +08:00
await focusDevChatInput ( ExtensionContextHolder . provider ? . view ( ) ! ) ;
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-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-12-20 12:57:55 +08:00
const homePath = process . env . HOME || process . env . USERPROFILE || '' ;
const sysDirPath = path . join ( homePath , '.chat' , 'workflows' , 'sys' ) ;
const pluginDirPath = path . join ( UiUtilWrapper . extensionPath ( ) , 'workflowsCommands' ) ; // Adjust this path as needed
const devchat = new DevChat ( ) ;
if ( ! fs . existsSync ( sysDirPath ) ) {
await copyDirectory ( pluginDirPath , sysDirPath ) ;
}
2023-08-30 16:39:47 +08:00
2023-12-18 14:41:12 +08:00
// Check if ~/.chat/workflows/sys directory exists
if ( ! fs . existsSync ( sysDirPath ) ) {
// Directory does not exist, wait for updateSysCommand to finish
await devchat . updateSysCommand ( ) ;
sendCommandListByDevChatRun ( ) ;
} else {
// Directory exists, execute sendCommandListByDevChatRun immediately
2024-01-09 08:37:31 +08:00
await sendCommandListByDevChatRun ( ) ;
2023-12-18 14:41:12 +08:00
// Then asynchronously execute updateSysCommand
2024-01-09 08:37:31 +08:00
await devchat . updateSysCommand ( ) ;
await sendCommandListByDevChatRun ( ) ;
2023-12-18 14:41:12 +08:00
}
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 ( ) ) ;
2024-01-10 10:56:56 +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-12-12 16:39:08 +08:00
export function registerDevChatChatCommand ( context : vscode.ExtensionContext ) {
let disposable = vscode . commands . registerCommand ( 'DevChat.Chat' , async ( message : string ) = > {
ensureChatPanel ( context ) ;
2024-01-23 16:10:23 +08:00
if ( ! ExtensionContextHolder . provider ? . view ( ) ) {
// wait 2 seconds
await new Promise ( ( resolve , reject ) = > {
setTimeout ( ( ) = > {
resolve ( true ) ;
} , 2000 ) ;
} ) ;
}
2023-12-12 16:39:08 +08:00
chatWithDevChat ( ExtensionContextHolder . provider ? . view ( ) ! , message ) ;
} ) ;
context . subscriptions . push ( disposable ) ;
}
2024-01-18 17:52:55 +08:00
export function registerHandleUri ( context : vscode.ExtensionContext ) {
context . subscriptions . push ( vscode . window . registerUriHandler ( {
2024-01-25 10:35:14 +08:00
async handleUri ( uri ) {
2024-01-18 17:52:55 +08:00
// 解析 URI 并执行相应的操作
if ( uri . path . includes ( 'accesskey' ) ) {
const accessKey = uri . path . split ( '/' ) [ 2 ] ;
const modelConfig : any = UiUtilWrapper . getConfiguration ( "devchat" , 'Provider.devchat' ) ;
const providerConfigNew :any = { }
if ( Object . keys ( modelConfig ) . length !== 0 ) {
for ( const key of Object . keys ( modelConfig || { } ) ) {
const property = modelConfig ! [ key ] ;
providerConfigNew [ key ] = property ;
}
}
providerConfigNew . access_key = accessKey ;
vscode . workspace . getConfiguration ( "devchat" ) . update ( "Provider.devchat" , providerConfigNew , vscode . ConfigurationTarget . Global ) ;
2024-01-24 17:58:08 +08:00
ensureChatPanel ( context ) ;
2024-01-25 10:35:14 +08:00
await new Promise ( ( resolve , reject ) = > {
setTimeout ( ( ) = > {
resolve ( true ) ;
} , 1000 ) ;
} ) ;
2024-01-24 17:58:08 +08:00
ExtensionContextHolder . provider ? . reloadWebview ( ) ;
2024-01-18 17:52:55 +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
} ;