我为LowCodeEngine低代码写了个AI插件,降低低代码上手难度。

项目体验地址:dbfu.github.io/easy-builde...

项目仓库地址:github.com/dbfu/easy-b...

功能还比较简陋,后面慢慢完善,大家可以先体验一下AI助手功能。

目前插件是和低代码项目放在一起,后面我会给单独拆出来,让想用这个插件的朋友也能在自己项目里快速使用。目前物料使用的是官方的基于antd的物料,我只改了几个组件,让组件对外暴露了方法和值。

前言

上一篇分享了一个不用写代码也能使用低代码的插件,但是还是有一些上手难度,比如要理解事件、方法等概念。现在AI大模型可以帮助我们处理很多事情,那AI和低代码结合能碰撞出什么样的火花呢,下面和大家分享一下我的实践。

建议先看一下上篇文章

我为LowCodeEngine低代码引擎写了个插件,让不懂代码的产品人员也能自己开发页面了。

思考

前面使用插件后配置流程虽然简化了很多,但是还不是不够简单,如果能借助AI分析用户的输入,自动实现前面所有的动作就好了。

下面来分析一下点击按钮,打开弹框这个动作,其实只需要给它转换为下面这样的数据结构,我们就能解析并对接我们前面开发的事件流插件。

json 复制代码
{
  "componentName": "Button",
  "event": "onClick",
  "action": {
    "type": "ComponentMethod",
    "componentName": "Modal",
    "method": "open"
  }
}

componentName: 触发事件的组件

event: 事件

action: 执行的动作

可能有人会有疑问,这里只知道组件名称,不知道是哪个组件,怎么给组件绑定事件呢,我这里的处理是,如果画布中只出现一个当前类型的组件时,就取这个组件,如果画布中没有当前类型的组件,自动生成一个插入到画布中,如果画布中有多个当前类型的组件,会让用户选择一个。

也就是说当用户输入了点击按钮,打开弹框,会自动在画布中添加一个按钮和弹框,并且给按钮的点击事件绑定打开弹框的动作。

demo演示

点击按钮,打开弹框

点击按钮,打开弹框,一秒后关闭。

实现原理

分析用户输入

靠我们自己写代码去解析用户输入,基本不可能,因为用户可以随便输入,没有固定的格式,这时候我们需要借助 AI 大模型,帮我们分析用户的输入,然后转换为上面的数据结构。

分析用户输入这里我验证了两套方案,一个是langchain+ zod,还有一个是微软出的typechat,他们都可以实现把用户输入以json格式输出。

初始化后端框架

因为要对外暴露接口,所以要用到后端框架,我这里后端框架采用midway

创建一个midway项目

sh 复制代码
npm init midway@latest -y

推荐选择koa-v3模版,项目名称自己输入。

langchain + zod

在service里封装一个方法,调用 langchain 库,根据 zod 定义的模型,解析用户输入,最终返回和模型一致的数据结构。用的大模型是gpt-3.5-turbo,因为某些网站可以免费获取到3.5的密钥。

ts 复制代码
async formatInputToEventFlows(input: string) {
    const parser = StructuredOutputParser.fromZodSchema(schema);

    const chain = RunnableSequence.from([
      PromptTemplate.fromTemplate(
        fs
          .readFileSync(this.koaApp.getAppDir() + '/template.txt', 'utf-8')
          .toString()
      ),
      new OpenAI({
        temperature: 0.5,
        modelName: 'gpt-3.5-turbo',
        configuration: {
          // openapi 代理地址
          baseURL: process.env.OPENAI_ENDPOINT,
          // api key
          apiKey: process.env.OPENAI_API_KEY,
        },
      }),
      parser,
    ]);

    const response = await chain.invoke({
      input,
      format_instructions: parser.getFormatInstructions(),
    });

    return response;
}

看一下 schema 怎么定义的

ts 复制代码
import { z } from "zod";

export const showMessageAction = z
  .object({
    name: z.literal("showMessage"),
    type: z.enum(["success", "error"]),
    content: z.string().describe("消息内容"),
  })
  .describe("显示消息");

export const openPageAction = z
  .object({
    name: z.literal("openPage"),
    url: z.string().describe("打开页面的url"),
  })
  .describe("打开页面");

export const componentAction = z
  .object({
    name: z.literal("ComponentMethod"),
    component: z.union([
      z.literal("Button").describe("按钮组件"),
      z.literal("Modal").describe("弹窗组件"),
      z.literal("Input").describe("输入框组件"),
    ]),
    method: z.string().describe("组件方法"),
  })
  .describe("调用组件方法");

export const schema = z.object({
  componentName: z.string().nullish().describe("触发事件的组件,可以为空"),
  eventName: z.union([
    z.literal("success").describe("成功事件"),
    z.literal("error").describe("失败事件"),
    z.string().describe(`当前组件对应的事件。
      点击(onClick)
      确认按钮点击事件(onOK), 取消按钮点击事件(onCancel),弹出事件(onShow)
      获取焦点事件(onFocus), 失去焦点事件(onBlur)`),
  ]),
  action: z
    .union([showMessageAction, openPageAction, componentAction])
    .describe("执行的动作"),
  children: z.lazy(() =>
    schema.nullish().describe("后续事件,没有后续可以为空")
  ),
});

大模型之所以能根据用户输入解析到对应的字段上,完全靠 describe 方法里的字段描述。

看一下Prompt定义,format_instructions和input是变量,发送给openai的时候,会被替换成zod的模型描述和用户输入。

txt 复制代码
你是一个低代码平台
希望你能根据用户输入,分析用户的行为。

{format_instructions}
{input}

要求children字段数据格式和JSON Schema保持一致

typechat

使用typechat解析用户输入

ts 复制代码
 async formatInputToEventFlows(input: string) {
    const model = createLanguageModel({
      OPENAI_MODEL: 'gpt-3.5-turbo',
      OPENAI_API_KEY: process.env.OPENAI_API_KEY,
      OPENAI_ENDPOINT: process.env.OPENAI_ENDPOINT,
    });
    const schema = fs.readFileSync(
      path.join(this.koaApp.getAppDir(), 'src/service/schema.ts'),
      'utf8'
    );
    const translator = createJsonTranslator<EventFlow>(
      model,
      schema,
      'EventFlow'
    );

    const response = await translator.translate(input);

    if (response.success) {
      return response.data;
    } else {
      throw new Error('error');
    }
  }

看一下schema定义,typechat支持使用ts定义,在字段上加注释就行了。

ts 复制代码
export type SuccessEvent = 'success';
export type ErrorEvent = 'error';
export type ButtonEvent = 'onClick';
export type ModalEvent = 'onOk' | 'onCancel' | 'onShow';

export type EventFlow = {
  // 当前触发的事件的组件,可以为空
  componentName?: 'Button' | 'Modal' | null;
  // 事件
  event: SuccessEvent | ErrorEvent | ButtonEvent | ModalEvent;
  // 动作
  action: {
    onSuccess?: EventFlow['action'];
    onError?: EventFlow['action'];
  } & (
    | {
        name: 'ComponentMethod';
        component: 'Modal';
        method: 'open' | 'close';
      }
    | {
        name: 'showMessage';
        type: 'success' | 'error';
        content: string;
      }
    | {
        name: 'openPage';
        url: string;
      }
    | {
        // 执行定时器
        name: 'setTimeout';
        // 毫秒
        timer: number;
        // 多少毫秒后执行的动作
        onSuccess?: EventFlow['action'];
      }
  );
};

我开始使用的是langchain+zod方案,这种方式定义模型的方式比较复杂并且返回值还不稳定,后面把方案换成了typechat,定义模型也比较简单,不需要太多的注释,并且还准确率也高,返回的结果比较稳定。

接口测试

输入:点击按钮,打开弹框

输出:

json 复制代码
{
    "componentName": "Button",
    "event": "onClick",
    "action": {
        "name": "ComponentMethod",
        "component": "Modal",
        "method": "open"
    }
}

输入:点击按钮,打开弹框。成功后显示提示,提示内容为 hello。显示成功后关闭弹框。

输出:

json 复制代码
{
    "componentName": "Button",
    "event": "onClick",
    "action": {
        "name": "ComponentMethod",
        "component": "Modal",
        "method": "open",
        "onSuccess": {
            "name": "showMessage",
            "type": "success",
            "content": "hello",
            "onSuccess": {
                "name": "ComponentMethod",
                "component": "Modal",
                "method": "close"
            }
        }
    }
}

输入:点击按钮,打开弹框。一秒后,关闭弹框。

输出:

json 复制代码
{
    "componentName": "Button",
    "event": "onClick",
    "action": {
        "name": "ComponentMethod",
        "component": "Modal",
        "method": "open",
        "onSuccess": {
            "name": "setTimeout",
            "timer": 1000,
            "onSuccess": {
                "name": "ComponentMethod",
                "component": "Modal",
                "method": "close"
            }
        }
    }
}

使用typechat方案测试了很多遍,输出还是挺稳定的。

前端解析

前端采用对话的方式,用户输入完需求后,向后端发送请求,从后端拿到通过大模型格式化后的数据结构,在前端代码中再去解析数据结构,生成组件和绑定事件。

对话框实现

对话内容分为用户和AI,消息类型定义

ts 复制代码
// 用户消息类型
interface UserMessage {
  id: string,
  role: 'user',
  content: string,
  status: 'success',
}

// AI消息类型
export interface AIMessage {
  id: string,
  role: 'ai',
  content: AIContent,
  status: 'loading' | 'success' | 'error'
}

export interface AIContent {
  componentName?: string,
  event: string,
  action: {
    onSuccess?: AIContent['action'],
    onError?: AIContent['action'],
  } & {
    name: string,
    [k: string]: any,
  },
}

布局使用的是flex布局

关于输入框回车事件有个需要注意的地方,中文输入法输入英文单词按回车键,也会触发回车事件,这种情况可以用keyCode来判断,英文下的回车keyCode是13,中文输入法下的keyCode是229,可以用这个判断。

解析后端返回的结构

先解析触发事件的组件。如果当前组件类型画布中没有,那么自动给用户生成一个。如果有多个,需要用户选择一个。如果只有一个,就选择当前这个。

再解析事件。判断组件是否有当前事件,如果没有就终止

解析动作。根据不同的动作生成事件流数据结构

解析后续事件。如果 有onSuccess或onError不为空,表示还有后续事件,递归解析。

解析完成后,生成的事件流。

点击按钮,打开弹框为例,看一下自动生成事件流数据结构。

json 复制代码
{
  "onClick": {
    "type": "flow",
    "value": {
      "id": "root",
      "label": "开始",
      "type": "start",
      "children": [
        {
          "type": "action",
          "id": "deadd458-1357-4d64-9810-6169458cdb51",
          "label": "组件方法",
          "key": "action",
          "config": {
            "type": "ComponentMethod",
            "config": {
              "componentId": "db1a11a4-47a7-4c88-b86c-025aca7fe168",
              "method": "open"
            }
          }
        }
      ]
    }
  }
}

最后调用node的setPropValue方法,把生成的事件流绑定到当前组件的事件属性上。

未来规划

上面通过AI大模型实现了用户输入需求,自动生成组件和组件事件绑定。虽然例子比较简单,但是我觉得这一块还是有很大的前景的,我也在慢慢完善。

关于这一块我的规划是:

  • 继续完善组件事件绑定动作这一方式,支持更复杂的用户输入,比如自动生成条件,和组件属性绑定变量。

    举个例子,当用户输入点击按钮,检验输入框的内容是否为邮箱格式,如果是邮箱格式,发送邮件。如果不是提示错误消息,消息内容为邮箱格式不正确,这里解析用户输入的内容,需要加上条件,当然还有更复杂的场景,我慢慢完善吧。

  • 还有一个方向,用户直接输入功能需求,使用大模型直接生成低代码的schema,这样的方式对比上面更简单,比如输入我要使用阿里的LowCodeEngine低代码平台开发员工管理系统,帮我生成一个员工管理页面低代码Schema。,AI会自动生成一个增删改查的Schema,拿过来简单解析一下就能对接到低代码平台了。

json 复制代码
{
  "title": "员工管理系统",
  "components": [
    {
      "type": "Table",
      "name": "employeeTable",
      "dataSource": {
        "type": "API",
        "api": "/api/employees"
      },
      "columns": [
        {
          "title": "姓名",
          "field": "name",
          "type": "Text"
        },
        {
          "title": "职位",
          "field": "position",
          "type": "Text"
        },
        {
          "title": "部门",
          "field": "department",
          "type": "Text"
        },
        {
          "title": "电子邮件",
          "field": "email",
          "type": "Email"
        },
        {
          "title": "电话号码",
          "field": "phone",
          "type": "Text"
        }
      ],
      "actions": [
        {
          "type": "Button",
          "title": "新增",
          "action": {
            "type": "Dialog",
            "title": "新增员工",
            "form": {
              "type": "Form",
              "fields": [
                {
                  "name": "name",
                  "label": "姓名",
                  "type": "TextInput"
                },
                {
                  "name": "position",
                  "label": "职位",
                  "type": "TextInput"
                },
                {
                  "name": "department",
                  "label": "部门",
                  "type": "TextInput"
                },
                {
                  "name": "email",
                  "label": "电子邮件",
                  "type": "EmailInput"
                },
                {
                  "name": "phone",
                  "label": "电话号码",
                  "type": "TextInput"
                }
              ],
              "submit": {
                "type": "API",
                "api": "/api/employees/create"
              }
            }
          }
        },
        {
          "type": "Button",
          "title": "编辑",
          "action": {
            "type": "Dialog",
            "title": "编辑员工信息",
            "dataSource": {
                "type": "API",
                "api": "/api/employees/{id}"
            },
            "form": {
              "type": "Form",
              "fields": [
                {
                  "name": "name",
                  "label": "姓名",
                  "type": "TextInput"
                },
                {
                  "name": "position",
                  "label": "职位",
                  "type": "TextInput"
                },
                {
                  "name": "department",
                  "label": "部门",
                  "type": "TextInput"
                },
                {
                  "name": "email",
                  "label": "电子邮件",
                  "type": "EmailInput"
                },
                {
                  "name": "phone",
                  "label": "电话号码",
                  "type": "TextInput"
                }
              ],
              "submit": {
                "type": "API",
                "api": "/api/employees/update"
              }
            }
          }
        }
      ]
    }
  ]
}
  • 还有一个方向,如果用户想开发一个系统,直接输入我想开发XXX系统,利用AI大模型给出表模型,然后我们解析模型之间的关系自动生成接口和页面,这样就可以轻松完成一个系统。比如用户输入我要开发一套员工管理系统,帮我生成一个员工管理系统的所有表结构,以json格式输出。
json 复制代码
{
  "tables": [
    {
      "name": "Employee",
      "fields": [
        {
          "name": "id",
          "type": "int",
          "primaryKey": true,
          "autoIncrement": true
        },
        {
          "name": "name",
          "type": "varchar",
          "length": 255
        },
        {
          "name": "email",
          "type": "varchar",
          "length": 255,
          "unique": true
        },
        {
          "name": "phone",
          "type": "varchar",
          "length": 255,
          "nullable": true
        },
        {
          "name": "positionId",
          "type": "int",
          "reference": {
            "table": "Position",
            "field": "id"
          }
        }
      ]
    },
    {
      "name": "Department",
      "fields": [
        {
          "name": "id",
          "type": "int",
          "primaryKey": true,
          "autoIncrement": true
        },
        {
          "name": "name",
          "type": "varchar",
          "length": 255
        }
      ]
    },
    {
      "name": "EmployeeDepartment",
      "fields": [
        {
          "name": "employeeId",
          "type": "int",
          "reference": {
            "table": "Employee",
            "field": "id"
          }
        },
        {
          "name": "departmentId",
          "type": "int",
          "reference": {
            "table": "Department",
            "field": "id"
          }
        }
      ],
      "primaryKey": [
        "employeeId",
        "departmentId"
      ]
    },
    {
      "name": "Position",
      "fields": [
        {
          "name": "id",
          "type": "int",
          "primaryKey": true,
          "autoIncrement": true
        },
        {
          "name": "title",
          "type": "varchar",
          "length": 255
        }
      ]
    }
  ]
}

上面是AI回复的结果,我们拿到这个json数据自动生成接口和页面也不是不行。

这几个就是我未来的规划,打算慢慢完善他们。

最后

项目体验地址:dbfu.github.io/easy-builde...

项目仓库地址:github.com/dbfu/easy-b...

功能目前还比较简陋,后面会慢慢完善,比如添加更多的物料、对接后端接口、让AI支持更复杂的用户输入等。

目前插件是和低代码项目放在一起,后面我会给单独拆出来,让想用这个插件的朋友也能在自己项目里快速使用。

本文正在参加阿里低代码引擎征文活动

相关推荐
辻戋7 小时前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保7 小时前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun8 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp8 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.9 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl11 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫13 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友13 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理15 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻15 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js