我为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支持更复杂的用户输入等。

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

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

相关推荐
Мартин.4 分钟前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。1 小时前
案例-表白墙简单实现
前端·javascript·css
数云界2 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd2 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常2 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer2 小时前
Vite:为什么选 Vite
前端
小御姐@stella2 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing2 小时前
【React】增量传输与渲染
前端·javascript·面试
eHackyd2 小时前
前端知识汇总(持续更新)
前端
万叶学编程5 小时前
Day02-JavaScript-Vue
前端·javascript·vue.js