Support env var and prompt input for command

This commit is contained in:
bobo.yang 2023-06-02 09:58:44 +08:00
parent e06e35f8f9
commit b2ba9e148d
10 changed files with 153 additions and 97 deletions

View File

@ -36,7 +36,7 @@ export interface Command {
pattern: command.pattern,
description: command.description,
handler: async (commandName: string, userInput: string) => {
return CustomCommands.getInstance().handleCommand(commandName);
return CustomCommands.getInstance().handleCommand(commandName, userInput);
}
};
if (command.show || includeHide) {
@ -52,14 +52,14 @@ export interface Command {
// 转义特殊字符
const escapedPattern = commandObj.pattern.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
const commandPattern = new RegExp(
`\\/(${escapedPattern.replace('{{prompt}}', '(.+?)')})`,
`\\/(${escapedPattern.replace('\\{\\{prompt\\}\\}', '\\{\\{(.+?)\\}\\}')})`,
'g'
);
const matches = Array.from(text.matchAll(commandPattern));
const replacements = await Promise.all(
matches.map(async (match) => {
const matchedUserInput = match[1];
const matchedUserInput = match[2];
return await commandObj.handler(commandObj.name, matchedUserInput);
})
);

View File

@ -30,26 +30,35 @@ class CustomCommands {
this.commands = [];
try {
const subDirs = fs.readdirSync(workflowsDir, { withFileTypes: true })
const extensionDirs = fs.readdirSync(workflowsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
for (const dir of subDirs) {
const settingsPath = path.join(workflowsDir, dir, '_setting_.json');
for (const extensionDir of extensionDirs) {
const commandDir = path.join(workflowsDir, extensionDir, 'command');
if (fs.existsSync(commandDir)) {
const commandSubDirs = fs.readdirSync(commandDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
for (const commandSubDir of commandSubDirs) {
const settingsPath = path.join(commandDir, commandSubDir, '_setting_.json');
if (fs.existsSync(settingsPath)) {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
const command: Command = {
name: dir,
name: commandSubDir,
pattern: settings.pattern,
description: settings.description,
message: settings.message,
default: settings.default,
show: settings.show === undefined ? "true" : settings.show,
instructions: settings.instructions
instructions: settings.instructions.map((instruction: string) => path.join(commandDir, commandSubDir, instruction))
};
this.commands.push(command);
}
}
}
}
} catch (error) {
// 显示错误消息
logger.channel()?.error(`Failed to parse commands: ${error}`);
@ -71,7 +80,7 @@ class CustomCommands {
}
public handleCommand(commandName: string): string {
public handleCommand(commandName: string, userInput: string): string {
// 获取命令对象,这里假设您已经有一个方法或属性可以获取到命令对象
const command = this.getCommand(commandName);
if (!command) {
@ -80,13 +89,39 @@ class CustomCommands {
return '';
}
// 构建instructions列表字符串
let commandMessage = command.message;
if (userInput && userInput.length > 0) {
// userInput is "['aa', 'bb]" like string
// parse userInput to array
// handle eval exception
try {
const userInputArray = eval(userInput);
// replace command message $1 with userInputArray[0], $2 with userInputArray[1] and so on
for (let i = 0; i < userInputArray.length; i++) {
commandMessage = commandMessage.replace(`$${i + 1}`, userInputArray[i]);
}
} catch (error) {
logger.channel()?.error(`Failed to parse user input: ${userInput} error: ${error}`);
logger.channel()?.show();
return '';
}
}
// replace ${Name} with enviroment var Name
const envVarRegex = /\${(\w+)}/g;
commandMessage = commandMessage.replace(envVarRegex, (match, p1) => {
return process.env[p1] || '';
});
// build instrctions
const instructions = command!.instructions
.map((instruction: string) => `[instruction|./.chat/workflows/${command.name}/${instruction}]`)
.map((instruction: string) => `[instruction|${instruction}]`)
.join(' ');
// 返回结果字符串
return `${instructions} ${command!.message}`;
return `${instructions} ${commandMessage}`;
}
}

View File

@ -23,6 +23,8 @@ describe('CustomCommands', () => {
// Mock the file system with two directories, one with _setting_.json and one without
mockFs({
'workflows': {
"some": {
"command": {
'command1': {
'_setting_.json': JSON.stringify({
pattern: 'command1',
@ -36,7 +38,9 @@ describe('CustomCommands', () => {
'command2': {
// No _setting_.json file
},
},
}
}
}
});
const workflowsDir = path.join(process.cwd(), 'workflows');
@ -54,6 +58,7 @@ describe('CustomCommands', () => {
},
];
expectedResult[0].instructions = [path.join(workflowsDir, 'some', 'command', 'command1', 'instruction1'), path.join(workflowsDir, 'some', 'command', 'command1', 'instruction2')];
expect(customCommands['commands']).to.deep.equal(expectedResult);
});
@ -100,7 +105,23 @@ describe('CustomCommands', () => {
};
customCommands.regCommand(command);
const result = customCommands.handleCommand('test');
expect(result).to.equal('[instruction|./.chat/workflows/test/instruction1] [instruction|./.chat/workflows/test/instruction2] Test message');
const result = customCommands.handleCommand('test', '');
expect(result).to.equal('[instruction|instruction1] [instruction|instruction2] Test message');
});
it('should handle a custom command with args', () => {
const command: Command = {
name: 'test',
pattern: 'test {{prompt}}',
description: 'Test command',
message: 'Test message "$1","$2"',
default: false,
show: true,
instructions: ['instruction1', 'instruction2'],
};
customCommands.regCommand(command);
const result = customCommands.handleCommand('test', '["v1", "v2"]');
expect(result).to.equal('[instruction|instruction1] [instruction|instruction2] Test message "v1","v2"');
});
});