前段时间无意中看到 vercel 已经推出了利用大模型根据用户语言生成页面,并且可以根据需求不断调整页面,思考了一下如果是我,我该怎么做完成这个事情。
本着能白嫖就不花钱的原则,没有调 GPT 的 API,因为 GPTs 能够存储 KnowLedge 作为上下文进行分析,所以打算写一个最简单的'低代码'的 demo,只要能表达出意思就好了。
V1.0
准备代码
先用了 js,发现效果不好,GPT4 无法直接通过"不自洽"代码猜到我的一些意图,可能 GPT 没有联想和想象的能力(也可能是这个能力不成熟所以没放出来),不纠结这个问题,改成 ts,写好注释。
tsx
interface Container {
/** 容器名称 */
containerName: string;
/** 容器包含组件 */
components: {
/** 组件名称 */
text: string;
/** 组件类型 */
component: string;
}[];
/** 子容器 */
children?: Container[];
}
const Login: Container[] = [
{
containerName: "Form",
components: [
{
text: "用户名",
component: "Input",
},
{
text: "密码",
component: "Password",
},
],
children: [
{
containerName: "Action",
components: [
{
text: "登录",
component: "Button",
},
{
text: "忘记密码",
component: "Button",
},
],
},
],
},
];
const BaseComponents = [
{
type: "Input",
cnName: "输入框",
html: "<input></input>",
},
{
type: "Password",
cnName: "密码输入框",
html: '<input type="password"></input>',
},
{
type: "Select",
cnName: "密码输入框",
html: "<select></select>",
},
{
type: "Button",
cnName: "按钮",
html: "<button></button>",
},
];
生成注册页
ME: 你好,根据我的 Login 的配置,生成一个注册表单
GPT:
根据您的配置文件,登录表单(Login)包含以下元素:
容器名称为"Form",包含两个组件: 用户名(Input 类型) 密码(Password 类型) 子容器名称为"Action",包含两个按钮: 登录(Button 类型) 忘记密码(Button 类型) 为了创建一个注册表单,我们可以基于登录表单的配置,加入额外的必要字段。通常,注册表单会包含用户名、密码、确认密码、邮箱等字段。下面是一个基于您现有配置修改的注册表单:
tsx
interface Container {
/** 容器名称 */
containerName: string;
/** 容器包含组件 */
components: {
/** 组件名称 */
text: string;
/** 组件类型 */
component: string;
}[];
/** 子容器 */
children?: Container[];
}
const Registration: Container[] = [
{
containerName: "Form",
components: [
{
text: "用户名",
component: "Input",
},
{
text: "密码",
component: "Password",
},
{
text: "确认密码",
component: "Password",
},
{
text: "邮箱",
component: "Input",
},
],
children: [
{
containerName: "Action",
components: [
{
text: "注册",
component: "Button",
},
{
text: "已有账号?登录",
component: "Button",
},
],
},
],
},
];
这个配置添加了确认密码和邮箱的输入框,并将子容器中的"登录"和"忘记密码"按钮替换为"注册"和"已有账号?登录"按钮,以适应注册场景的需求。请根据您的实际需求进一步调整或添加其他必要字段。
可以发现 GPT 能领会了我的意思,并且生成了一个结构类似的代码,继续
添加 Button
ME: OK,很好,现在我想在注册页面的左上角加一个返回登录页的按钮
GPT:
tsx
const Registration: Container[] = [
{
containerName: "BackButton",
components: [
{
text: "返回登录",
component: "Button",
},
],
},
{
containerName: "Form",
components: [
{
text: "用户名",
component: "Input",
},
{
text: "密码",
component: "Password",
},
{
text: "确认密码",
component: "Password",
},
{
text: "邮箱",
component: "Input",
},
],
children: [
{
containerName: "Action",
components: [
{
text: "注册",
component: "Button",
},
{
text: "已有账号?登录",
component: "Button",
},
],
},
],
},
];
为了精简内容只展示GPT回复的代码部分,后续保持一致
GPT 完全明白 Container 的作用,并且可以借此完成想要的功能,虽然命名上和我想要的有点不一样,但是应该可以通过增加约定来进行优化。
添加 Logo
ME: 我发现返回登录已经有了,左上角应该放置一个 Logo 图片
GPT:
tsx
const Registration: Container[] = [
{
containerName: "Logo",
components: [
{
text: "Logo", // 这里的text属性可以用于辅助标识,实际展示的是图片
component: "Image",
imagePath: "path/to/your/logo.png", // 假设的Logo图片路径
},
],
},
{
containerName: "Form",
components: [
{
text: "用户名",
component: "Input",
},
{
text: "密码",
component: "Password",
},
{
text: "确认密码",
component: "Password",
},
{
text: "邮箱",
component: "Input",
},
],
children: [
{
containerName: "Action",
components: [
{
text: "注册",
component: "Button",
},
{
text: "已有账号?登录",
component: "Button",
},
],
},
],
},
];
如果没有定义 interface 和注释(主要应该是注释),GPT 还是不能理解 BaseComponents 中的内容进行联想,但是他会尝试采用一些行业比较通用的方法去解决问题。现在先完善 BaseComponents 的类型定义和注释。
V2.0
准备代码
tsx
interface Container {
/** 容器名称 */
containerName: string;
/** 容器包含组件 */
components: {
/** 组件名称 */
text: string;
/** 组件类型 */
component: string;
}[];
/** 子容器 */
children?: Container[];
}
const Login: Container[] = [
{
containerName: "Form",
components: [
{
text: "用户名",
component: "Input",
},
{
text: "密码",
component: "Password",
},
],
children: [
{
containerName: "Action",
components: [
{
text: "登录",
component: "Button",
},
{
text: "忘记密码",
component: "Button",
},
],
},
],
},
];
interface Component {
/** 组件名称 */
name: string;
/** 组件中文 */
cnName: string;
/** 组件对应html代码 */
html: string;
}
const BaseComponents: Component[] = [
{
name: "Input",
cnName: "输入框",
html: "<input></input>",
},
{
name: "Password",
cnName: "密码输入框",
html: '<input type="password"></input>',
},
{
name: "Select",
cnName: "密码输入框",
html: "<select></select>",
},
{
name: "Button",
cnName: "按钮",
html: "<button></button>",
},
];
重新生成
ME: 我重新修改了 Knowledge,希望你根据新的 Knowledge 重新生成一个左上角带 Logo 的注册页
GPT:
tsx
interface Container {
/** 容器名称 */
containerName: string;
/** 容器包含组件 */
components: {
/** 组件名称 */
text: string;
/** 组件类型 */
component: string;
}[];
/** 子容器 */
children?: Container[];
}
const RegistrationPage: Container[] = [
{
containerName: "Header",
components: [
{
text: "Logo",
component: "Image",
},
],
},
{
containerName: "Form",
components: [
{
text: "用户名",
component: "Input",
},
{
text: "电子邮件",
component: "Email",
},
{
text: "密码",
component: "Password",
},
{
text: "确认密码",
component: "Password",
},
],
children: [
{
containerName: "Action",
components: [
{
text: "注册",
component: "Button",
},
],
},
],
},
];
export default RegistrationPage;
ME: Image 的组件没有定义,帮我定义一下 GPT:
tsx
interface ImageComponentProps {
/** 图片源地址 */
src: string;
/** 图片的替代文本 */
alt: string;
/** 应用于图片的CSS类 */
className?: string;
}
/** Image组件 */
const ImageComponent: React.FC<ImageComponentProps> = ({ src, alt, className }) => {
return <img src={src} alt={alt} className={className} />;
};
tsx
{
containerName: "Header",
components: [
{
text: "Logo",
component: "ImageComponent",
props: {
src: "logo的URL地址",
alt: "公司Logo",
className: "logo-class"
}
},
],
}
ME: 我还是希望你根据我的 Knowledge 去定义
GPT:
tsx
interface ImageProps {
/** 图片地址 */
src: string;
/** 图片替代文本 */
alt: string;
}
const Image: React.FC<ImageProps> = ({ src, alt }) => {
return <img src={src} alt={alt} style={{ maxWidth: "100%", height: "auto" }} />;
};
tsx
const RegistrationPage: Container[] = [
{
containerName: "Header",
components: [
{
text: "Logo",
component: "Image",
props: {
src: "path/to/your/logo.png",
alt: "Website Logo",
},
},
],
},
// 其他容器和组件的定义...
];
和图片一样只要涉及重新生成,必然会有些问题,把"已有账号?登录"给搞丢了
聊天中没有指明 BaseComponents 是定义组件的,并且代码其实没有实际关联上,导致 GPT 无法做这样的思考(GPT 的思考方式还是比较死板的,缺乏一些想象力),不过在实际代码中肯定不会这样去写,这里继续优化一下代码吧。
V3.0
准备代码
tsx
interface Component {
/** 组件名称 */
name: string;
/** 组件中文 */
cnName: string;
/** 组件对应html代码 */
html: string;
}
const Input: Component = {
name: "Input",
cnName: "输入框",
html: "<input></input>",
};
const Password: Component = {
name: "Password",
cnName: "密码输入框",
html: '<input type="password"></input>',
};
const Select: Component = {
name: "Select",
cnName: "密码输入框",
html: "<select></select>",
};
const Button: Component = {
name: "Button",
cnName: "按钮",
html: "<button></button>",
};
interface Container {
/** 容器名称 */
containerName: string;
/** 容器包含组件 */
components: {
/** 组件名称 */
text: string;
/** 组件类型 */
component: Component;
}[];
/** 子容器 */
children?: Container[];
}
const Login: Container[] = [
{
containerName: "Form",
components: [
{
text: "用户名",
component: Input,
},
{
text: "密码",
component: Password,
},
],
children: [
{
containerName: "Action",
components: [
{
text: "登录",
component: Button,
},
{
text: "忘记密码",
component: Button,
},
],
},
],
},
];
重新生成
ME: 很抱歉,我的代码写的不太好,请你根据我刚刚的要求再重新生成一个注册页
GPT:
tsx
interface Component {
/** 组件名称 */
name: string;
/** 组件中文 */
cnName: string;
/** 组件对应html代码 */
html: string;
}
const Logo: Component = {
name: "Logo",
cnName: "标志",
html: '<img src="logo路径" alt="Logo"></img>',
};
const Input: Component = {
name: "Input",
cnName: "输入框",
html: "<input></input>",
};
const Password: Component = {
name: "Password",
cnName: "密码输入框",
html: '<input type="password"></input>',
};
const Email: Component = {
name: "Email",
cnName: "邮箱输入框",
html: '<input type="email"></input>',
};
const Button: Component = {
name: "Button",
cnName: "按钮",
html: "<button></button>",
};
interface Container {
/** 容器名称 */
containerName: string;
/** 容器包含组件 */
components: {
/** 组件名称 */
text: string;
/** 组件类型 */
component: Component;
}[];
/** 子容器 */
children?: Container[];
}
const PageLayout: Container[] = [
{
containerName: "Header",
components: [
{
text: "Logo",
component: Logo,
},
],
},
{
containerName: "Form",
components: [
{
text: "用户名",
component: Input,
},
{
text: "密码",
component: Password,
},
{
text: "确认密码",
component: Password,
},
{
text: "邮箱",
component: Email,
},
],
children: [
{
containerName: "Action",
components: [
{
text: "注册",
component: Button,
},
],
},
],
},
];
关联上之后,GPT 立刻就聪明了,虽然定义方式和我设想的还是不太一样,但是借此可以推断只要上下文足够优秀,GPT 生成低代码产品就是工程化的问题。
总结
可以看到,当设计的代码能逻辑自洽,GPT 的分析能力完全可以生成一段'低代码'的代码,而且从物料到页面(延续到其他层面)应该都没问题,但是如何不同场景下去限定作用范围(这个作用范围不只是指具体功能,也可以是抽象层面的)可能会是一个需要重点考虑的问题。
从我的对话中,GPT 和生成图片类似,一旦涉及到重新生成,就会真的重新生成。在 V2.0 里,GPT 把返回登录按钮给搞丢了,之前用过 Vercel 的低代码平台,发现也一样有这样的问题。尤其是后期功能调教的时候,AI 的目的是降低门槛,门槛降低势必会有大量的用户不具备程序员思维,不能理解我要只是觉得不好看要重新生成一个 ABCD,完事了就只剩 ABC 了,然后再说把 D 加回来,发现这个 D 和原来的 D 不一样了。所以,限定作用范围本身也是需要 GPT 去理解和决策做的事情,如果通过代码层面进行决策的干预,那很不 AI。