Merge pull request #202 from devchat-ai/set_python
update devchat install
This commit is contained in:
commit
e1d939c7f7
20
package.json
20
package.json
@ -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"
|
||||
|
@ -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 () => {
|
||||
|
@ -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.`);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
32321
tools/get-pip.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user