65.9K
CodeProject 正在变化。 阅读更多。
Home

通过插件接口定制 AI 数字助手

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2020年9月7日

CPOL

9分钟阅读

viewsIcon

7403

downloadIcon

272

定制和构建用于 PC 的 AI 语音助手平台的插件(C#)

引言

如今,人工智能这个词充斥着整个世界(不知道还能流行多久),我想不如乘着这股浪潮,尝试一些新东西。我一直是CodeProject上的机器人爱好者,但在本文中,我们将深入研究一些新东西。

我们将用C#编写一个自定义插件,该插件可以让我们通过语音命令向打开的活动游戏窗口发送键盘按键。简而言之,就是通过语音命令控制游戏。

我写这篇文章的灵感来源于我正在使用的AI助手应用程序是由一个我非常熟悉的机器人开发框架支持的。

必备组件

Syn VA 框架和一个在线俄罗斯方块浏览器窗口。

在我们开始之前,我将做一个大胆的假设:您精通C# .NET编程,并且熟悉使用Visual Studio 2019(在本文撰写时)。

在深入阅读本文之前,您需要确保已预先安装一些东西。

  • Visual Studio 2019(或更高版本)- 我使用的是社区版,供您参考。
  • Syn VA 框架(最好安装最新版本)-社区版应该就足够了。
  • Oryzer Studio - 一个免费提供的基于流程的编程平台,我将在文章后面用到它。

本文的部分内容使用了我另一篇文章中的一些术语:使用Oscova在C#中创建本地离线机器人

入门

我们将遵循具体的步骤来实现创建插件的目标。

  • 我们将从创建一个C#插件库开始。
  • 将该库放置在Assistant的Plugins目录下的正确文件夹中。
  • 使用Oryzer Studio为我们的虚拟助手添加一些知识。
  • 指定语音命令和按键绑定。
  • 向AI助手添加语音命令示例。
  • 最后,我们将运行应用程序,看看我们的语音命令是否在游戏上生效。

创建插件项目

那么,让我们开始吧,启动Visual Studio并创建一个合适的项目来创建我们的插件。

  • 打开 Visual Studio。
  • 选择WPF用户控件库(.NET Framework)

  • 点击下一步,并将项目名称指定为Syn.VA.Plugins.GameCommander
  • 确保目标框架设置为.NET Framework 4.6.1或更高版本。

我添加前缀Syn.VA.Plugins.*的原因是,AI助手应用程序只会在Plugins目录中扫描以此前缀开头的文件作为插件。

实现助手插件类

现在项目已准备就绪,我们将首先创建一个继承AssistantPlugin类的类。为此,请执行以下操作:

  • 右键单击项目名称。
  • 选择添加,然后选择类...

将类命名为GameCommanderPlugin,并添加以下代码:

namespace Syn.VA.Plugins.GameCommander
{
    //Inherit the AssistantPlugin class
    public class GameCommanderPlugin : AssistantPlugin
    {
        public GameCommanderPlugin(VirtualAssistant assistant)
            : base(assistant) { }

        //Return null for now as our plugin doesn't have
        //any editable settings.
        public override T GetPanel<T>(params object[] parameters)
        {
            return null;
        }
    }
}

这是VA框架扫描的第一个类,用于判断一个类库是否确实是VA插件。我们在这里不需要做太多事情,因为我们不操作任何核心助手功能。相反,我们将扩展它。

创建功能节点

如前所述,我们将创建一个称为Node的东西。在基于流程的编程(FBP)环境中,节点是一段功能代码,在称为Workspace的图中是可视可交互的。我们在Oryzer Studio中创建工作区项目。

现在已经弄清楚了,我们将向项目中添加一个新文件夹,并称之为Nodes,在该文件夹内,我们将创建一个新的文件并将其命名为GameCommanderNode。我们之所以这样命名,是因为最终我们的节点将接收特定的键盘按键值并将其发送到活动窗口。

让我们逐步创建GameCommanderNode类。将以下代码粘贴到文件中:

using Syn.Workspace;
using Syn.Workspace.Attributes;
using Syn.Workspace.Events;
using Syn.Workspace.Nodes;

namespace Syn.VA.Plugins.GameCommander.Nodes
{
    [Node(Category = CategoryTypes.VaFramework, DisplayName = "Game Commander")]
    public class GameCommanderNode : FunctionNode
    {
        public GameCommanderNode(WorkspaceGraph workspace) : base(workspace) { }

        public override void OnTriggered(object sender, TriggerEventArgs eventArgs)
        {
            
        }
    }
}

在这里,我们继承了FunctionNode,并添加了接受WorkspaceGraph作为参数的构造函数。

为了实现FunctionNode,我们还需要创建一个特殊的OnTriggered()方法,该方法接受一组参数。在此方法内将是我们节点被触发时将执行的代码。

稍后在文章中一切都会变得清晰,所以不要过于纠结。

接下来,我们将向节点添加一个端口。这个端口将负责存储特定语音命令的按键组合。

[InputPort]
[PortEditor]
public string KeyCombinations
{
    get => _keyCombinations;
    set { _keyCombinations = value; OnPropertyChanged(); }
}

在上面的代码中,我们用InputPortPortEditor属性装饰了我们的KeyCombinations属性。这些特殊的属性使得我们的类属性在Oryzer Studio的视觉编辑器中可见。

顺便说一句,PortEditor属性指定如果应用程序对该属性类型有内置编辑器,则应用程序可以自由显示或渲染它。

现在,让我们将OnTriggered()函数扩展为我们希望节点在被触发时执行的操作。

但在此之前,我们将不得不使用System.Windows.Forms命名空间。为此,您需要手动引用它。

为此,请在引用管理器中勾选System.Windows.Forms,如下所示:

现在我们添加了正确的引用,请将OnTriggered()方法更改为以下内容:

public override void OnTriggered(object sender, TriggerEventArgs eventArgs)
{
    try
    {
        SendKeys.SendWait(KeyCombinations);
        VirtualAssistant.Instance
            .Logger.Info<GameCommanderNode>($"Sending Keys: \"{KeyCombinations}\"");

        //Sequential Trigger.
        RaiseTriggerFlow(sender, eventArgs);
    }
    catch (Exception e)
    {
        Workspace.Logger.Error<GameCommanderNode>(e);
    }
}

以上所有代码都是调用SendKeys.SendWait()方法并传入KeyCombinations值。

顺便说一句,调用RaiseTriggerFlow()方法是为了继续所谓的顺序节点触发,即如果调用此节点,则在执行其代码块后应调用子节点。

整体代码

using System;
using System.Windows.Forms;
using Syn.Workspace;
using Syn.Workspace.Attributes;
using Syn.Workspace.Events;
using Syn.Workspace.Nodes;

namespace Syn.VA.Plugins.GameCommander.Nodes
{
    [Node(Category = CategoryTypes.VaFramework, DisplayName = "Game Commander")]
    public class GameCommanderNode : FunctionNode
    {
        private string _keyCombinations;
        public GameCommanderNode(WorkspaceGraph workspace) : base(workspace) { }

        [InputPort]
        [PortEditor]
        public string KeyCombinations
        {
            get => _keyCombinations;
            set { _keyCombinations = value; OnPropertyChanged(); }
        }

        public override void OnTriggered(object sender, TriggerEventArgs eventArgs)
        {
            try
            {
                SendKeys.SendWait(KeyCombinations);
                VirtualAssistant.Instance
                    .Logger.Info<GameCommanderNode>($"Sending Keys: \"{KeyCombinations}\"");

                //Sequential Trigger.
                RaiseTriggerFlow(sender, eventArgs);
            }
            catch (Exception e)
            {
                Workspace.Logger.Error<GameCommanderNode>(e);
            }
        }
    }
}

为节点创建单独的插件?

这可能看起来有点奇怪,但如果我们尝试理解节点如何被导入到Oryzer Studio进行交互,就会更有意义。我们创建了GameCommanderNode以使其在Oryzer Studio中可用。此节点仅在单独的WorkspacePlugin实现公开它时才可见(截至本文撰写之日,不知何故)。

我们现在不需要深入研究这一点,所以让我们创建一个Workspace Plugin

创建一个名为GameCommanderWorkspacePlugin的新文件,并添加以下代码:

using Syn.Workspace;

namespace Syn.VA.Plugins.GameCommander.Nodes
{
    public class GameCommanderWorkspacePlugin : WorkspacePlugin
    {
        public GameCommanderWorkspacePlugin(WorkspaceGraph workspace) : base(workspace)
        {
            //Register the workspace node in this plugin.
            workspace.Nodes.RegisterType<GameCommanderNode>();
        }
    }
}

放置插件文件

现在C#编码部分已完成。我们将编译项目,以便创建一个动态链接库文件(DLL),并将其放置在Assistant的插件目录中。为此:

  • F6键构建项目。
  • 打开Bin/Release目录,并复制Syn.VA.Plugins.GameCommanderPlugin.dll

  • 运行VA Framework Assistant。
  • 通过单击左上角的齿轮图标打开设置面板
  • 选择系统,然后单击打开工作目录,如下所示:

  • 浏览到Plugins目录,并将复制的文件粘贴到那里。

现在我们完成了插件创建部分。让我们继续进行一些基于流程的编程,以创建将执行我们创建的节点的命令。

基于流程的编程

继续我们开发工作的下一个主要部分,现在我们将创建一个可视化图,在其中我们将指定一组命令并将某些键盘按键绑定到它们。当使用命令时,我们的节点将被触发,进而将指定的键盘按键发送到活动窗口。

如果您看过我之前关于创建机器人知识的文章(如下文所述),您将更容易理解接下来的内容。

我将向您展示如何创建第一个意图,当用户说向上箭头时将触发我们创建的节点。

为此,让我们:

  • Ctrl+F搜索Oscova Bot节点。
  • 将其拖到工作区。
  • 拖动DialogIntent Node
  • 按照下图连接它们:

到目前为止,我们所做的是创建了一个名为up_arrow_intent的意图,当指定表达式匹配用户输入时,将调用此意图。

接下来,我们设置一个响应节点,并将“Game Commander”节点连接到它。

  • 再次,按Ctrl+F搜索Response节点。
  • 将其连接到Intent节点。
  • 添加一个Expression节点,并将值设置为{up arrow}
  • 连接一个Input Text节点,并将文本值设置为Up Arrow Pressed
  • 最后,添加Game Commander节点,并将按键组合值设置为{UP}

我现在已经展示了如何为用户命令向上箭头添加一个命令(意图)。

虽然您可能已经猜到了,但我还是会为向下、向左和向右箭头命令添加屏幕截图。

下箭头按键命令

左箭头按键命令

右箭头按键命令

现在机器人的知识库已经准备好,我们将保存此工作区项目,然后将其复制并粘贴到AI助手的Knowledge目录下。

  • Ctrl+S保存工作区。
  • 将项目文件命名为Game-Commander.west
  • 复制此文件。
  • 打开VA Framework。
  • 通过单击左上角的齿轮图标打开设置面板
  • 选择系统,然后单击打开工作目录

  • 浏览到Knowledge目录,并将复制的文件粘贴到那里。

现在我们已经完成了知识库部分,因为粘贴的文件将在下次重新启动时被AI助手读取和导入。

添加语音命令

现在,我们将继续向AI助手添加语音命令,以便我们可以使用语音识别来触发我们在Workspace项目中存储的命令。

  • 打开VA Framework。
  • 单击左上角的设置面板图标。
  • 选择语音>语音命令,然后单击右下角的+图标。
  • 添加4个语音命令:向上箭头向下箭头向左箭头向右箭头

  • 单击右下角的保存按钮。
  • 重新启动数字助手。

让我们测试一下

现在我们已经完成了插件的创建、工作区知识库项目的创建以及语音命令的设置。

  • 让我们打开VA Framework。
  • 单击语音图标。
  • 打开俄罗斯方块在线游戏。
  • 保持浏览器窗口处于焦点状态,并使用语音命令进行导航。

就这样!我们终于构建了一个自定义插件,它使我们能够通过我们助手的ASR(自动语音识别)来玩俄罗斯方块。

关注点

与我以前关于机器人开发的文章不同,这是我第一次撰写关于插件开发的文章。我发现玩转精巧的软件技术很有趣。我还有一部分内容没有讲到,那就是与VA Framework中的设置管理有关的内容。我目前正在研究它。如果我有什么有趣的新发现,以后可能会将此文章扩展为第二部分。

历史

  • 2020年9月7日 - 初始发布
© . All rights reserved.