Merge pull request #504 from devchat-ai/new-wf

[dev] Support workflow engine 2.0
This commit is contained in:
boob.yang 2024-05-13 21:51:35 +08:00 committed by GitHub
commit 94274fae74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 61 additions and 147 deletions

2
gui

@ -1 +1 @@
Subproject commit fc781cece340c22ecf301c612e64efde3313a2dc
Subproject commit fc5e916ec69fd0dcc18fe4ea92352ae8dd3d9e67

View File

@ -192,7 +192,7 @@ export function registerInstallCommandsCommand(
"DevChat.InstallCommands",
async () => {
const homePath = process.env.HOME || process.env.USERPROFILE || "";
const sysDirPath = path.join(homePath, ".chat", "workflows", "sys");
const sysDirPath = path.join(homePath, ".chat", "scripts");
const pluginDirPath = path.join(
UiUtilWrapper.extensionPath(),
"workflowsCommands"
@ -204,7 +204,7 @@ export function registerInstallCommandsCommand(
await copyDirectory(pluginDirPath, sysDirPath);
}
// Check if ~/.chat/workflows/sys directory exists
// Check if ~/.chat/scripts directory exists
if (!fs.existsSync(sysDirPath)) {
// Directory does not exist, wait for updateSysCommand to finish
await devchat.updateSysCommand();

View File

@ -5,38 +5,6 @@ import { ApiKeyManager } from '../util/apiKey';
import DevChat from '../toolwrapper/devchat';
export interface Command {
name: string;
pattern: string;
description: string;
path: string;
args: number;
recommend: number;
handler: (commandName: string, userInput: string) => Promise<string>;
}
async function getCommandListByDevChatRun(includeHide: boolean = false): Promise<Command[]> {
// load commands from CustomCommands
let newCommands: Command[] = [];
const devChat = new DevChat();
const commandList = await devChat.commands();
commandList.forEach(command => {
const commandObj: Command = {
name: command.name,
pattern: command.name,
description: command.description,
path: command.path,
recommend: command.recommend,
args: 0,
handler: async (commandName: string, userInput: string) => { return ''; }
};
newCommands.push(commandObj);
});
return newCommands;
}
let existPannel: vscode.WebviewPanel|vscode.WebviewView|undefined = undefined;
regInMessage({command: 'regCommandList'});
@ -44,17 +12,8 @@ regOutMessage({command: 'regCommandList', result: [{name: '', pattern: '', descr
export async function getWorkflowCommandList(message: any, panel: vscode.WebviewPanel|vscode.WebviewView): Promise<void> {
existPannel = panel;
const commandList = await getCommandListByDevChatRun();
const commandCovertedList = commandList.map(command => {
if (command.args > 0) {
// replace {{prompt}} with {{["",""]}}, count of "" is args
const prompt = Array.from({length: command.args}, () => "");
command.pattern = command.pattern.replace('{{prompt}}', '{{' + JSON.stringify(prompt) + '}}');
}
return command;
});
MessageHandler.sendMessage(panel, { command: 'regCommandList', result: commandCovertedList });
const commandList = await new DevChat().commands();
MessageHandler.sendMessage(panel, { command: 'regCommandList', result: commandList });
return;
}

View File

@ -11,52 +11,24 @@ function copyFileSync(source: string, target: string) {
if (!fs.existsSync(target)) {
fs.writeFileSync(target, data);
}
}
function copyDirSync(source: string, target: string) {
}
function copyDirSync(source: string, target: string) {
// 创建目标目录
fs.mkdirSync(target, { recursive: true });
// 遍历目录中的所有文件和子目录
const files = fs.readdirSync(source);
for (const file of files) {
const sourcePath = path.join(source, file);
const targetPath = path.join(target, file);
const stats = fs.statSync(sourcePath);
if (stats.isDirectory()) {
// 递归拷贝子目录
copyDirSync(sourcePath, targetPath);
} else {
// 拷贝文件
copyFileSync(sourcePath, targetPath);
}
const sourcePath = path.join(source, file);
const targetPath = path.join(target, file);
const stats = fs.statSync(sourcePath);
if (stats.isDirectory()) {
// 递归拷贝子目录
copyDirSync(sourcePath, targetPath);
} else {
// 拷贝文件
copyFileSync(sourcePath, targetPath);
}
}
}
export function createChatDirectoryAndCopyInstructionsSync(extensionUri: vscode.Uri) {
const workspaceRoot = UiUtilWrapper.workspaceFoldersFirstPath();
if (!workspaceRoot) {
return;
}
const chatWorkflowsDirPath = path.join(workspaceRoot, '.chat', 'workflows');
const instructionsSrcPath = path.join(extensionUri.fsPath, 'workflows');
// if workflows directory exists, return
if (fs.existsSync(chatWorkflowsDirPath)) {
return ;
}
try {
if (!fs.existsSync(chatWorkflowsDirPath)) {
fs.mkdirSync(chatWorkflowsDirPath, {recursive: true});
} else {
// return;
}
copyDirSync(instructionsSrcPath, chatWorkflowsDirPath);
} catch (error) {
logger.channel()?.error('Error occurred while creating the .chat directory and copying workflows:', error);
logger.channel()?.show();
}
}
}

View File

@ -1,13 +1,9 @@
import * as vscode from 'vscode';
import * as path from 'path';
import WebviewManager from './webviewManager';
import '../handler/handlerRegister';
import handleMessage from '../handler/messageHandler';
import { createChatDirectoryAndCopyInstructionsSync } from '../init/chatConfig';
import { ExtensionContextHolder } from '../util/extensionContext';
import { UiUtilWrapper } from '../util/uiUtil';
import { ChatContextManager } from '../context/contextManager';
export class DevChatViewProvider implements vscode.WebviewViewProvider {
@ -23,20 +19,7 @@ export class DevChatViewProvider implements vscode.WebviewViewProvider {
return this._view;
}
reloadCustomDefines() {
const workspaceDir = UiUtilWrapper.workspaceFoldersFirstPath();
if (workspaceDir) {
const workflowsDir = path.join(workspaceDir!, '.chat', 'workflows');
ChatContextManager.getInstance().loadCustomContexts(workflowsDir);
}
}
resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken): void {
// 创建 .chat 目录并复制 workflows
createChatDirectoryAndCopyInstructionsSync(ExtensionContextHolder.context?.extensionUri!);
this.reloadCustomDefines();
this._view = webviewView;
this._webviewManager = new WebviewManager(webviewView.webview, this._context.extensionUri);
@ -46,7 +29,6 @@ export class DevChatViewProvider implements vscode.WebviewViewProvider {
public reloadWebview(): void {
if (this._webviewManager) {
this.reloadCustomDefines();
this._webviewManager.reloadWebviewContent();
}
}

View File

@ -48,7 +48,7 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St
progressBar.update(`Checking dependencies: Success`, 0);
progressBar.end();
// download workflows from github or gitlab
// install devchat workflow commands
if (!hasInstallCommands) {
hasInstallCommands = true;
await vscode.commands.executeCommand('DevChat.InstallCommands');

View File

@ -12,6 +12,8 @@ import { getFileContent } from '../util/commonUtil';
import * as toml from '@iarna/toml';
import { DevChatConfig } from '../util/config';
import { getMicromambaUrl } from '../util/python_installer/conda_url';
const readFileAsync = fs.promises.readFile;
const envPath = path.join(__dirname, '..', '.env');
@ -208,7 +210,10 @@ class DevChat {
// eslint-disable-next-line @typescript-eslint/naming-convention
"PYTHONUTF8":1,
// eslint-disable-next-line @typescript-eslint/naming-convention
"PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages"
"PYTHONPATH": UiUtilWrapper.extensionPath() + "/tools/site-packages",
// eslint-disable-next-line @typescript-eslint/naming-convention
"DEVCHAT_PROXY": DevChatConfig.getInstance().get('DEVCHAT_PROXY') || "",
"MAMBA_BIN_PATH": getMicromambaUrl(),
};
const pythonApp = DevChatConfig.getInstance().get('python_for_chat') || "python3";
@ -260,6 +265,7 @@ class DevChat {
// eslint-disable-next-line @typescript-eslint/naming-convention
...llmModelData.api_base? { "OPENAI_API_BASE": llmModelData.api_base, "OPENAI_BASE_URL": llmModelData.api_base } : {},
"DEVCHAT_PROXY": DevChatConfig.getInstance().get('DEVCHAT_PROXY') || "",
"MAMBA_BIN_PATH": getMicromambaUrl(),
};
// build process options
@ -422,44 +428,34 @@ class DevChat {
async loadRecommendCommands(): Promise<string[]> {
try {
// 获取用户的主目录
const userHomeDir = os.homedir();
// 构建配置文件路径
const configFilePath = path.join(userHomeDir, '.chat', 'workflows', 'sys', 'configuration.toml');
// 异步读取配置文件内容
const configFileContent = await readFileAsync(configFilePath, { encoding: 'utf8' });
const args = ["-m", "devchat", "workflow", "config", "--json"];
// 解析TOML配置文件内容并返回命令列表
return this.parseConfigFile(configFileContent);
} catch (err) {
console.error('Failed to load recommend commands:', err);
return [];
}
}
const {code, stdout, stderr} = await this.runCommand(args);
// 解析TOML配置文件内容
private parseConfigFile(content: string): string[] {
interface Config {
recommend?: {
workflows?: string[];
};
assertValue(code !== 0, stderr || `Command exited with ${code}`);
if (stderr.trim() !== "") {
logger.channel()?.warn(`${stderr}`);
}
let workflowConfig;
try {
workflowConfig = JSON.parse(stdout.trim());
} catch (error) {
logger.channel()?.error('Failed to parse commands JSON:', error);
return [];
}
return workflowConfig.recommend?.workflows || [];
} catch (error: any) {
logger.channel()?.error(`Error: ${error.message}`);
logger.channel()?.show();
return [];
}
try {
const parsedData = toml.parse(content) as Config;
if (parsedData.recommend && parsedData.recommend.workflows) {
return parsedData.recommend.workflows;
}
} catch (err) {
logger.channel()?.error(`Error parsing TOML content: ${err}`);
}
return [];
}
async commands(): Promise<CommandEntry[]> {
async commands(): Promise<any[]> {
try {
const args = ["-m", "devchat", "run", "--list"];
const args = ["-m", "devchat", "workflow", "list", "--json"];
const {code, stdout, stderr} = await this.runCommand(args);
@ -475,11 +471,10 @@ class DevChat {
logger.channel()?.error('Failed to parse commands JSON:', error);
return [];
}
// 确保每个CommandEntry对象的recommend字段默认为-1
const recommendCommands = await this.loadRecommendCommands();
commands = commands.map((cmd: CommandEntry) => ({
commands = commands.map((cmd: any) => ({
...cmd,
recommend: recommendCommands.indexOf(cmd.name),
}));
@ -494,7 +489,7 @@ class DevChat {
async updateSysCommand(): Promise<string> {
try {
const args = ["-m", "devchat", "run", "--update-sys"];
const args = ["-m", "devchat", "workflow", "update"];
const {code, stdout, stderr} = await this.runCommand(args);

View File

@ -36,11 +36,17 @@ export async function installDevchat(): Promise<string> {
const pythonApp = path.join(pythonTargetPath, "python.exe");
const pythonPathFile = path.join(pythonTargetPath, "python311._pth");
const sitepackagesPath = path.join(UiUtilWrapper.extensionPath(), "tools", "site-packages");
const userHomeDir = os.homedir();
const WORKFLOWS_BASE_NAME = "scripts";
const workflow_base_path = path.join(userHomeDir, ".chat", WORKFLOWS_BASE_NAME);
const new_python_path = [workflow_base_path, sitepackagesPath].join("\n");
// read content in pythonPathFile
let content = fs.readFileSync(pythonPathFile, { encoding: 'utf-8' });
// replace %PYTHONPATH% with sitepackagesPath
content = content.replace(/%PYTHONPATH%/g, sitepackagesPath);
content = content.replace(/%PYTHONPATH%/g, new_python_path);
// write content to pythonPathFile
fs.writeFileSync(pythonPathFile, content);

2
tools

@ -1 +1 @@
Subproject commit 1db2a69a5f1cd1576e582d9d7b3ae5a2d88392c2
Subproject commit a761c6a462fb0d0e5eb8ec3328dba2015d455fe6

@ -1 +1 @@
Subproject commit daea79e719527e93a5262a2b8f1d1cb83c47ebe7
Subproject commit a9015f2a37a25e324a8e9e41a9c12b75a8b78e37