Merge pull request #202 from devchat-ai/set_python

update devchat install
This commit is contained in:
boob.yang 2023-07-06 11:54:57 +08:00 committed by GitHub
commit e1d939c7f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 32573 additions and 52 deletions

View File

@ -2,7 +2,7 @@
"name": "devchat",
"displayName": "DevChat",
"description": "Write prompts, not code",
"version": "0.1.8",
"version": "0.1.17",
"icon": "assets/devchat.png",
"publisher": "merico",
"engines": {
@ -131,6 +131,19 @@
}
},
"description": "Where is the devchat binary located?"
},
"DevChat.PythonPath": {
"type": "string",
"default": "",
"input": {
"type": "file",
"filter": {
"All files": [
"python*"
]
}
},
"description": "Which Python interpreter to use with DevChat?"
}
}
},
@ -196,6 +209,11 @@
"title": "Input Access Key",
"category": "DevChat"
},
{
"command": "DevChat.PythonPath",
"title": "Set Python Path",
"category": "DevChat"
},
{
"command": "devchat.openChatPanel",
"title": "DevChat"

View File

@ -5,6 +5,8 @@ import { TopicManager } from '../topic/topicManager';
import { TopicTreeDataProvider, TopicTreeItem } from '../panel/topicView';
import { FilePairManager } from '../util/diffFilePairs';
import { ApiKeyManager } from '../util/apiKey';
import { UiUtilWrapper } from '../util/uiUtil';
import { isValidApiKey } from '../handler/historyMessagesBase';
function registerOpenChatPanelCommand(context: vscode.ExtensionContext) {
@ -72,6 +74,10 @@ export function registerApiKeySettingCommand(context: vscode.ExtensionContext) {
placeHolder: "Set OPENAI_API_KEY (or DevChat Access Key)"
}) ?? '';
if (!isValidApiKey(passwordInput)) {
UiUtilWrapper.showErrorMessage("You access key is invalid!");
return ;
}
ApiKeyManager.writeApiKeySecret(passwordInput);
})
);
@ -148,6 +154,21 @@ export function regReloadTopicCommand(context: vscode.ExtensionContext) {
);
}
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) {
vscode.workspace.getConfiguration("DevChat").update("PythonPath", pythonPath, vscode.ConfigurationTarget.Global);
}
})
);
}
export function regApplyDiffResultCommand(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('devchat.applyDiffResult', async () => {

View File

@ -1,43 +1,99 @@
// src/contributes/commandsBase.ts
import { UiUtilWrapper } from "../util/uiUtil";
import { runCommand } from "../util/commonUtil";
import { logger } from "../util/logger";
import path from "path";
let pipxPathStatus = '';
let devchatStatus = '';
function locateCommand(command): string | undefined {
try {
// split lines and choose first line
const binPaths = runCommand(`where ${command}`).toString().trim().split('\n');
return binPaths[0].trim();
} catch (error) {
try {
const binPaths = runCommand(`which ${command}`).toString().trim().split('\n');
return binPaths[0].trim();
} catch (error) {
return undefined;
}
}
}
export function checkDevChatDependency(pythonCommand: string): boolean {
let pipxBinPath: string | undefined = undefined;
try {
const binPath = getPipxEnvironmentPath(pythonCommand);
pipxBinPath = binPath;
if (binPath) {
updateEnvironmentPath(binPath);
const error_status = `Updated pipx environment path.`;
if (pipxPathStatus !== error_status) {
logger.channel()?.info(error_status);
pipxPathStatus = error_status;
}
} else {
logger.channel()?.info(`Failed to obtain the pipx environment path.`);
const error_status = `Failed to obtain the pipx environment path.`;
if (pipxPathStatus !== error_status) {
logger.channel()?.warn(error_status);
logger.channel()?.show();
pipxPathStatus = error_status;
}
return false;
}
} catch (error) {
// DevChat dependency check failed
// log out detail error message
logger.channel()?.info(`Failed to check DevChat dependency due to error: ${error}`);
const error_status = `Failed to check DevChat dependency due to error: ${error}`;
if (pipxPathStatus !== error_status) {
logger.channel()?.warn(error_status);
logger.channel()?.show();
pipxPathStatus = error_status;
}
return false;
}
try {
// Check if DevChat is installed
runCommand('devchat --help');
const pipxDevChat = path.join(pipxBinPath!, 'devchat');
runCommand(`"${pipxDevChat}" --help`);
UiUtilWrapper.updateConfiguration('DevChat', 'DevChatPath', pipxDevChat);
const error_status = `DevChat has installed.`;
if (devchatStatus !== error_status) {
logger.channel()?.info(error_status);
devchatStatus = error_status;
}
return true;
} catch(error) {
logger.channel()?.error(`Failed to check DevChat dependency due to error: ${error}`);
const error_status = `Failed to check DevChat dependency due to error: ${error}`;
if (devchatStatus !== error_status) {
logger.channel()?.warn(error_status);
logger.channel()?.show();
devchatStatus = error_status;
}
return false;
}
}
export function getValidPythonCommand(): string | undefined {
function getDefaultPythonCommand(): string | undefined {
try {
runCommand('python3 -V');
return 'python3';
return locateCommand('python3');
} catch (error) {
try {
const version = runCommand('python -V');
if (version.includes('Python 3')) {
return 'python';
return locateCommand('python');
}
return undefined;
} catch (error) {
@ -46,23 +102,45 @@ export function getValidPythonCommand(): string | undefined {
}
}
export function getPipxEnvironmentPath(pythonCommand: string): string | null {
// Get pipx environment
const pipxEnvOutput = runCommand(`${pythonCommand} -m pipx environment`).toString();
const binPathRegex = /PIPX_BIN_DIR=\s*(.*)/;
export function getValidPythonCommand(): string | undefined {
try {
const pythonCommand = UiUtilWrapper.getConfiguration('DevChat', 'PythonPath');
if (pythonCommand) {
return pythonCommand;
}
// Get BIN path from pipx environment
const match = pipxEnvOutput.match(binPathRegex);
if (match && match[1]) {
return match[1];
} else {
return null;
const defaultPythonCommand = getDefaultPythonCommand();
if (defaultPythonCommand) {
UiUtilWrapper.updateConfiguration('DevChat', 'PythonPath', defaultPythonCommand);
}
return defaultPythonCommand;
} catch (error) {
return undefined;
}
}
export function getPipxEnvironmentPath(pythonCommand: string): string | undefined {
// Get pipx environment
try {
const pipxEnvOutput = runCommand(`"${pythonCommand}" -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]) {
return match[1];
} else {
return undefined;
}
} catch (error) {
return undefined;
}
}
function updateEnvironmentPath(binPath: string): void {
// Add BIN path to PATH
if (process.env.PATH?.indexOf(binPath) === undefined) {
if (process.env.PATH?.indexOf(binPath) === undefined || process.env.PATH?.indexOf(binPath) < 0) {
process.env.PATH = `${binPath}:${process.env.PATH}`;
logger.channel()?.info(`Added ${binPath} to PATH.`);
}

View File

@ -13,6 +13,7 @@ import {
regReloadTopicCommand,
regApplyDiffResultCommand,
registerStatusBarItemClickCommand,
regPythonPathCommand,
} from './contributes/commands';
import { regLanguageContext } from './contributes/context';
import { regDevChatView, regTopicView } from './contributes/views';
@ -51,5 +52,7 @@ function activate(context: vscode.ExtensionContext) {
regSelectTopicCommand(context);
regReloadTopicCommand(context);
regApplyDiffResultCommand(context);
regPythonPathCommand(context);
}
exports.activate = activate;

View File

@ -1,6 +1,7 @@
import * as vscode from 'vscode';
import { dependencyCheck } from './statusBarViewBase';
import { logger } from '@/util/logger';
export function createStatusBarItem(context: vscode.ExtensionContext): vscode.StatusBarItem {
@ -13,26 +14,51 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St
statusBarItem.command = undefined;
// add a timer to update the status bar item
let runStatus = 0;
let continueTimes = 0
setInterval(async () => {
const [devchatStatus, apiKeyStatus] = await dependencyCheck();
if (devchatStatus !== 'ready') {
if (runStatus > 0 && continueTimes < 60) {
continueTimes += 1;
return ;
}
runStatus = 1;
continueTimes = 0;
try {
const [devchatStatus, apiKeyStatus] = await dependencyCheck();
if (devchatStatus !== 'ready') {
statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `${devchatStatus}`;
if (devchatStatus === 'Missing required dependency: Python3') {
statusBarItem.command = "devchat.PythonPath";
} else {
statusBarItem.command = undefined;
}
// set statusBarItem warning color
return;
}
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';
} catch (error) {
statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `${devchatStatus}`;
statusBarItem.tooltip = `Error: ${error}`;
statusBarItem.command = undefined;
// set statusBarItem warning color
return;
} finally {
runStatus = 0;
}
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

View File

@ -4,8 +4,9 @@ import { logger } from "../util/logger";
import { UiUtilWrapper } from "../util/uiUtil";
import { TopicManager } from "../topic/topicManager";
import { checkDevChatDependency, getValidPythonCommand } from "../contributes/commandsBase";
import { checkDevChatDependency, getPipxEnvironmentPath, getValidPythonCommand } from "../contributes/commandsBase";
import { ApiKeyManager } from '../util/apiKey';
import { CommandRun } from '../util/commonUtil';
@ -23,13 +24,17 @@ 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();
versionChanged = versionOld !== versionNew;
UiUtilWrapper.storeSecret("DevChatVersionOld", versionNew!);
try {
const versionOld = await UiUtilWrapper.secretStorageGet("DevChatVersionOld");
const versionNew = getExtensionVersion();
versionChanged = versionOld !== versionNew;
UiUtilWrapper.storeSecret("DevChatVersionOld", versionNew!);
isVersionChangeCompare = true;
logger.channel()?.info(`versionOld: ${versionOld}, versionNew: ${versionNew}, versionChanged: ${versionChanged}`);
isVersionChangeCompare = true;
logger.channel()?.info(`versionOld: ${versionOld}, versionNew: ${versionNew}, versionChanged: ${versionChanged}`);
} catch (error) {
isVersionChangeCompare = false;
}
}
const pythonCommand = getValidPythonCommand();
@ -49,17 +54,20 @@ export async function dependencyCheck(): Promise<[string, string]> {
// 1. not in a folder
// 2. dependence is invalid
// 3. ready
if (devchatStatus === '' || devchatStatus === 'Waiting for devchat installation to complete') {
if (devchatStatus === '' ||
devchatStatus === 'An error occurred during the installation of DevChat' ||
devchatStatus === 'DevChat has been installed') {
let bOk = true;
let devChat: string | undefined = UiUtilWrapper.getConfiguration('DevChat', 'DevChatPath');
if (!devChat) {
const pipxPath = getPipxEnvironmentPath(pythonCommand!);
if (!devChat || !pipxPath || devChat.indexOf(pipxPath) > -1) {
bOk = false;
}
if (!bOk) {
bOk = checkDevChatDependency(pythonCommand!);
}
if (bOk && versionChanged) {
if (bOk && versionChanged && !devChat) {
bOk = false;
}
@ -74,8 +82,27 @@ export async function dependencyCheck(): Promise<[string, string]> {
}
if (devchatStatus === 'not ready') {
// auto install devchat
UiUtilWrapper.runTerminal('DevChat Install', `${pythonCommand} ${UiUtilWrapper.extensionPath() + "/tools/install.py"}`);
devchatStatus = 'Waiting for devchat installation to complete';
const run = new CommandRun();
const options = {
cwd: UiUtilWrapper.workspaceFoldersFirstPath() || '.',
};
let errorInstall = false;
await run.spawnAsync(pythonCommand!, [UiUtilWrapper.extensionPath() + "/tools/install.py"], options,
(data) => {
logger.channel()?.info(data.trim());
},
(data) => {
errorInstall = true;
logger.channel()?.info(data.trim());
}, undefined, undefined);
// UiUtilWrapper.runTerminal('DevChat Install', `${pythonCommand} "${UiUtilWrapper.extensionPath() + "/tools/install.py"}"`);
if (errorInstall) {
devchatStatus = 'An error occurred during the installation of DevChat';
} else {
devchatStatus = 'DevChat has been installed';
}
isVersionChangeCompare = true;
}

View File

@ -179,7 +179,8 @@ class DevChat {
temperature: openaiTemperature,
stream: openaiStream,
}
}
};
// write to config file
const configPath = path.join(workspaceDir!, '.chat', 'config.json');
// write devchatConfig to configPath

View File

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

View File

@ -17,6 +17,10 @@ export class UiUtilVscode implements UiUtil {
public getConfiguration(key1: string, key2: string): string | undefined {
return vscode.workspace.getConfiguration(key1).get(key2);
}
public async updateConfiguration(key1: string, key2: string, value: string): Promise<void> {
await vscode.workspace.getConfiguration(key1).update(key2, value, vscode.ConfigurationTarget.Global);
}
public async secretStorageGet(key: string): Promise<string | undefined> {
try {
const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context!.secrets;

32321
tools/get-pip.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4,11 +4,28 @@ import sys
# replace python3 with sys.executable, we will do everything in the same envrionment
pythonCommand = sys.executable
print('Python command:', pythonCommand)
tools_dir = os.path.dirname(os.path.realpath(__file__))
def ensure_pip_installed():
print("install pip ...")
try:
subprocess.run([pythonCommand, "-m", "pip", "--version"], check=True)
return True
except Exception as e:
pass
try:
subprocess.run([pythonCommand, tools_dir + "/get-pip.py", "--force-reinstall"], check=True)
return True
except Exception as e:
print(e)
return False
def check_pipx_installed():
try:
subprocess.run(["pipx", "--version"], check=True)
subprocess.run([pythonCommand, "-m", "pipx", "--version"], check=True)
return True
except Exception as e:
return False
@ -16,7 +33,7 @@ def check_pipx_installed():
def install_pipx():
print("Installing pipx...")
try:
subprocess.run([pythonCommand, "-m", "pip", "install", "pipx", "--force"], check=True)
subprocess.run([pythonCommand, "-m", "pip", "install", "--user", "pipx", "--force-reinstall"], check=True)
print("pipx installed successfully.")
except subprocess.CalledProcessError as e:
print("Error installing pipx:", e, file=sys.stderr)
@ -38,7 +55,7 @@ def add_pipx_to_path():
def install_devchat():
print("Installing devchat...")
try:
subprocess.run(["pipx", "install", "devchat"], check=True)
subprocess.run([pythonCommand, "-m", "pipx", "install", "devchat", "--force"], check=True)
print("devchat installed successfully.")
except subprocess.CalledProcessError as e:
print("Error installing devchat:", e, file=sys.stderr)
@ -47,13 +64,14 @@ def install_devchat():
def upgrade_devchat():
print("Upgrading devchat...")
try:
subprocess.run(["pipx", "upgrade", "devchat"], check=True)
subprocess.run([pythonCommand, "-m", "pipx", "upgrade", "devchat"], check=True)
print("devchat upgraded successfully.")
except subprocess.CalledProcessError as e:
print("Error upgrading devchat:", e, file=sys.stderr)
sys.exit(1)
def main():
ensure_pip_installed()
if not check_pipx_installed():
install_pipx()
add_pipx_to_path()