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", "name": "devchat",
"displayName": "DevChat", "displayName": "DevChat",
"description": "Write prompts, not code", "description": "Write prompts, not code",
"version": "0.1.8", "version": "0.1.17",
"icon": "assets/devchat.png", "icon": "assets/devchat.png",
"publisher": "merico", "publisher": "merico",
"engines": { "engines": {
@ -131,6 +131,19 @@
} }
}, },
"description": "Where is the devchat binary located?" "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", "title": "Input Access Key",
"category": "DevChat" "category": "DevChat"
}, },
{
"command": "DevChat.PythonPath",
"title": "Set Python Path",
"category": "DevChat"
},
{ {
"command": "devchat.openChatPanel", "command": "devchat.openChatPanel",
"title": "DevChat" "title": "DevChat"

View File

@ -5,6 +5,8 @@ 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 { ApiKeyManager } from '../util/apiKey'; import { ApiKeyManager } from '../util/apiKey';
import { UiUtilWrapper } from '../util/uiUtil';
import { isValidApiKey } from '../handler/historyMessagesBase';
function registerOpenChatPanelCommand(context: vscode.ExtensionContext) { function registerOpenChatPanelCommand(context: vscode.ExtensionContext) {
@ -72,6 +74,10 @@ export function registerApiKeySettingCommand(context: vscode.ExtensionContext) {
placeHolder: "Set OPENAI_API_KEY (or DevChat Access Key)" placeHolder: "Set OPENAI_API_KEY (or DevChat Access Key)"
}) ?? ''; }) ?? '';
if (!isValidApiKey(passwordInput)) {
UiUtilWrapper.showErrorMessage("You access key is invalid!");
return ;
}
ApiKeyManager.writeApiKeySecret(passwordInput); 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) { export function regApplyDiffResultCommand(context: vscode.ExtensionContext) {
context.subscriptions.push( context.subscriptions.push(
vscode.commands.registerCommand('devchat.applyDiffResult', async () => { vscode.commands.registerCommand('devchat.applyDiffResult', async () => {

View File

@ -1,54 +1,129 @@
// src/contributes/commandsBase.ts // src/contributes/commandsBase.ts
import { UiUtilWrapper } from "../util/uiUtil";
import { runCommand } from "../util/commonUtil"; import { runCommand } from "../util/commonUtil";
import { logger } from "../util/logger"; 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 { export function checkDevChatDependency(pythonCommand: string): boolean {
let pipxBinPath: string | undefined = undefined;
try { try {
const binPath = getPipxEnvironmentPath(pythonCommand); const binPath = getPipxEnvironmentPath(pythonCommand);
pipxBinPath = binPath;
if (binPath) { if (binPath) {
updateEnvironmentPath(binPath); updateEnvironmentPath(binPath);
const error_status = `Updated pipx environment path.`;
if (pipxPathStatus !== error_status) {
logger.channel()?.info(error_status);
pipxPathStatus = error_status;
}
} else { } 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) { } catch (error) {
// DevChat dependency check failed // DevChat dependency check failed
// log out detail error message // 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 { try {
// Check if DevChat is installed // 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; return true;
} catch(error) { } 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; return false;
} }
} }
function getDefaultPythonCommand(): string | undefined {
try {
runCommand('python3 -V');
return locateCommand('python3');
} catch (error) {
try {
const version = runCommand('python -V');
if (version.includes('Python 3')) {
return locateCommand('python');
}
return undefined;
} catch (error) {
return undefined;
}
}
}
export function getValidPythonCommand(): string | undefined { export function getValidPythonCommand(): string | undefined {
try { try {
runCommand('python3 -V'); const pythonCommand = UiUtilWrapper.getConfiguration('DevChat', 'PythonPath');
return 'python3'; if (pythonCommand) {
} catch (error) { return pythonCommand;
try {
const version = runCommand('python -V');
if (version.includes('Python 3')) {
return 'python';
} }
return undefined;
const defaultPythonCommand = getDefaultPythonCommand();
if (defaultPythonCommand) {
UiUtilWrapper.updateConfiguration('DevChat', 'PythonPath', defaultPythonCommand);
}
return defaultPythonCommand;
} catch (error) { } catch (error) {
return undefined; return undefined;
} }
} }
}
export function getPipxEnvironmentPath(pythonCommand: string): string | null { export function getPipxEnvironmentPath(pythonCommand: string): string | undefined {
// Get pipx environment // Get pipx environment
const pipxEnvOutput = runCommand(`${pythonCommand} -m pipx environment`).toString(); try {
const pipxEnvOutput = runCommand(`"${pythonCommand}" -m pipx environment`).toString();
const binPathRegex = /PIPX_BIN_DIR=\s*(.*)/; const binPathRegex = /PIPX_BIN_DIR=\s*(.*)/;
// Get BIN path from pipx environment // Get BIN path from pipx environment
@ -56,13 +131,16 @@ export function getPipxEnvironmentPath(pythonCommand: string): string | null {
if (match && match[1]) { if (match && match[1]) {
return match[1]; return match[1];
} else { } else {
return null; return undefined;
}
} catch (error) {
return undefined;
} }
} }
function updateEnvironmentPath(binPath: string): void { function updateEnvironmentPath(binPath: string): void {
// Add BIN path to PATH // 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}`; process.env.PATH = `${binPath}:${process.env.PATH}`;
logger.channel()?.info(`Added ${binPath} to PATH.`); logger.channel()?.info(`Added ${binPath} to PATH.`);
} }

View File

@ -13,6 +13,7 @@ import {
regReloadTopicCommand, regReloadTopicCommand,
regApplyDiffResultCommand, regApplyDiffResultCommand,
registerStatusBarItemClickCommand, registerStatusBarItemClickCommand,
regPythonPathCommand,
} 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';
@ -51,5 +52,7 @@ function activate(context: vscode.ExtensionContext) {
regSelectTopicCommand(context); regSelectTopicCommand(context);
regReloadTopicCommand(context); regReloadTopicCommand(context);
regApplyDiffResultCommand(context); regApplyDiffResultCommand(context);
regPythonPathCommand(context);
} }
exports.activate = activate; exports.activate = activate;

View File

@ -1,6 +1,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { dependencyCheck } from './statusBarViewBase'; import { dependencyCheck } from './statusBarViewBase';
import { logger } from '@/util/logger';
export function createStatusBarItem(context: vscode.ExtensionContext): vscode.StatusBarItem { export function createStatusBarItem(context: vscode.ExtensionContext): vscode.StatusBarItem {
@ -13,12 +14,30 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St
statusBarItem.command = undefined; statusBarItem.command = undefined;
// add a timer to update the status bar item // add a timer to update the status bar item
let runStatus = 0;
let continueTimes = 0
setInterval(async () => { setInterval(async () => {
if (runStatus > 0 && continueTimes < 60) {
continueTimes += 1;
return ;
}
runStatus = 1;
continueTimes = 0;
try {
const [devchatStatus, apiKeyStatus] = await dependencyCheck(); const [devchatStatus, apiKeyStatus] = await dependencyCheck();
if (devchatStatus !== 'ready') { if (devchatStatus !== 'ready') {
statusBarItem.text = `$(warning)DevChat`; statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `${devchatStatus}`; statusBarItem.tooltip = `${devchatStatus}`;
if (devchatStatus === 'Missing required dependency: Python3') {
statusBarItem.command = "devchat.PythonPath";
} else {
statusBarItem.command = undefined; statusBarItem.command = undefined;
}
// set statusBarItem warning color // set statusBarItem warning color
return; return;
} }
@ -33,6 +52,13 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St
statusBarItem.text = `$(pass)DevChat`; statusBarItem.text = `$(pass)DevChat`;
statusBarItem.tooltip = `ready to chat`; statusBarItem.tooltip = `ready to chat`;
statusBarItem.command = 'devcaht.onStatusBarClick'; statusBarItem.command = 'devcaht.onStatusBarClick';
} catch (error) {
statusBarItem.text = `$(warning)DevChat`;
statusBarItem.tooltip = `Error: ${error}`;
statusBarItem.command = undefined;
} finally {
runStatus = 0;
}
}, 3000); }, 3000);
// Add the status bar item to the status bar // 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 { UiUtilWrapper } from "../util/uiUtil";
import { TopicManager } from "../topic/topicManager"; import { TopicManager } from "../topic/topicManager";
import { checkDevChatDependency, getValidPythonCommand } from "../contributes/commandsBase"; import { checkDevChatDependency, getPipxEnvironmentPath, getValidPythonCommand } from "../contributes/commandsBase";
import { ApiKeyManager } from '../util/apiKey'; import { ApiKeyManager } from '../util/apiKey';
import { CommandRun } from '../util/commonUtil';
@ -23,6 +24,7 @@ let isVersionChangeCompare: boolean|undefined = undefined;
export async function dependencyCheck(): Promise<[string, string]> { export async function dependencyCheck(): Promise<[string, string]> {
let versionChanged = false; let versionChanged = false;
if (isVersionChangeCompare === undefined) { if (isVersionChangeCompare === undefined) {
try {
const versionOld = await UiUtilWrapper.secretStorageGet("DevChatVersionOld"); const versionOld = await UiUtilWrapper.secretStorageGet("DevChatVersionOld");
const versionNew = getExtensionVersion(); const versionNew = getExtensionVersion();
versionChanged = versionOld !== versionNew; versionChanged = versionOld !== versionNew;
@ -30,6 +32,9 @@ export async function dependencyCheck(): Promise<[string, string]> {
isVersionChangeCompare = true; isVersionChangeCompare = true;
logger.channel()?.info(`versionOld: ${versionOld}, versionNew: ${versionNew}, versionChanged: ${versionChanged}`); logger.channel()?.info(`versionOld: ${versionOld}, versionNew: ${versionNew}, versionChanged: ${versionChanged}`);
} catch (error) {
isVersionChangeCompare = false;
}
} }
const pythonCommand = getValidPythonCommand(); const pythonCommand = getValidPythonCommand();
@ -49,17 +54,20 @@ export async function dependencyCheck(): Promise<[string, string]> {
// 1. not in a folder // 1. not in a folder
// 2. dependence is invalid // 2. dependence is invalid
// 3. ready // 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 bOk = true;
let devChat: string | undefined = UiUtilWrapper.getConfiguration('DevChat', 'DevChatPath'); let devChat: string | undefined = UiUtilWrapper.getConfiguration('DevChat', 'DevChatPath');
if (!devChat) { const pipxPath = getPipxEnvironmentPath(pythonCommand!);
if (!devChat || !pipxPath || devChat.indexOf(pipxPath) > -1) {
bOk = false; bOk = false;
} }
if (!bOk) { if (!bOk) {
bOk = checkDevChatDependency(pythonCommand!); bOk = checkDevChatDependency(pythonCommand!);
} }
if (bOk && versionChanged) { if (bOk && versionChanged && !devChat) {
bOk = false; bOk = false;
} }
@ -74,8 +82,27 @@ export async function dependencyCheck(): Promise<[string, string]> {
} }
if (devchatStatus === 'not ready') { if (devchatStatus === 'not ready') {
// auto install devchat // auto install devchat
UiUtilWrapper.runTerminal('DevChat Install', `${pythonCommand} ${UiUtilWrapper.extensionPath() + "/tools/install.py"}`); const run = new CommandRun();
devchatStatus = 'Waiting for devchat installation to complete'; 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; isVersionChangeCompare = true;
} }

View File

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

View File

@ -3,6 +3,7 @@ export interface UiUtil {
languageId(uri: string): Promise<string>; languageId(uri: string): Promise<string>;
workspaceFoldersFirstPath(): string | undefined; workspaceFoldersFirstPath(): string | undefined;
getConfiguration(key1: string, key2: string): string | undefined; getConfiguration(key1: string, key2: string): string | undefined;
updateConfiguration(key1: string, key2: string, value: string): Promise<void>;
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>;
@ -34,6 +35,9 @@ export class UiUtilWrapper {
public static getConfiguration(key1: string, key2: string): string | undefined { public static getConfiguration(key1: string, key2: string): string | undefined {
return this._uiUtil?.getConfiguration(key1, key2); 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> { public static async secretStorageGet(key: string): Promise<string | undefined> {
return await this._uiUtil?.secretStorageGet(key); 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 { public getConfiguration(key1: string, key2: string): string | undefined {
return vscode.workspace.getConfiguration(key1).get(key2); 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> { public async secretStorageGet(key: string): Promise<string | undefined> {
try { try {
const secretStorage: vscode.SecretStorage = ExtensionContextHolder.context!.secrets; 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 # replace python3 with sys.executable, we will do everything in the same envrionment
pythonCommand = sys.executable 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(): def check_pipx_installed():
try: try:
subprocess.run(["pipx", "--version"], check=True) subprocess.run([pythonCommand, "-m", "pipx", "--version"], check=True)
return True return True
except Exception as e: except Exception as e:
return False return False
@ -16,7 +33,7 @@ def check_pipx_installed():
def install_pipx(): def install_pipx():
print("Installing pipx...") print("Installing pipx...")
try: 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.") print("pipx installed successfully.")
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print("Error installing pipx:", e, file=sys.stderr) print("Error installing pipx:", e, file=sys.stderr)
@ -38,7 +55,7 @@ def add_pipx_to_path():
def install_devchat(): def install_devchat():
print("Installing devchat...") print("Installing devchat...")
try: try:
subprocess.run(["pipx", "install", "devchat"], check=True) subprocess.run([pythonCommand, "-m", "pipx", "install", "devchat", "--force"], check=True)
print("devchat installed successfully.") print("devchat installed successfully.")
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print("Error installing devchat:", e, file=sys.stderr) print("Error installing devchat:", e, file=sys.stderr)
@ -47,13 +64,14 @@ def install_devchat():
def upgrade_devchat(): def upgrade_devchat():
print("Upgrading devchat...") print("Upgrading devchat...")
try: try:
subprocess.run(["pipx", "upgrade", "devchat"], check=True) subprocess.run([pythonCommand, "-m", "pipx", "upgrade", "devchat"], check=True)
print("devchat upgraded successfully.") print("devchat upgraded successfully.")
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print("Error upgrading devchat:", e, file=sys.stderr) print("Error upgrading devchat:", e, file=sys.stderr)
sys.exit(1) sys.exit(1)
def main(): def main():
ensure_pip_installed()
if not check_pipx_installed(): if not check_pipx_installed():
install_pipx() install_pipx()
add_pipx_to_path() add_pipx_to_path()