基于AI的动态界面交互系统概念探索
场景预设
假如我有一个房间,房间中卧室,厨房,家具有有空调,洗衣机,灯泡....
写成结构化的形式就是:
{title="房间-家具表",rooms=[{name="卧室",id="bed_room",items=[{name="床头灯",id="bed_light"values=[{name="开关",id="open"type="bool"},{name="亮度",id="brightness",type="num",range=[0,100]}]},{name="空调",id="air_conditioner",....}]},{name="厨房",id="kitchen"items=[{name="抽油烟机",id="range_hood"values=[{name="模式",id="mode",type="enum",enum=["default","power","off","quiet"]},{name="强度",id="power",type="num",range=[0,5]}]},......]}......]
}
上述结构称为 "元数据",在代码中写作metaRoomData
而我们的目地是编写一个智能家居系统,通过网页软件实现对家中电器的控制
基于上述元数据定义,我们很容易想到组织api的方法:
set/room_id/furniture_id/value_id?value=${newValue}
get/room_id/furniture_id/value_id
通过上面这对get/set,我们可以实现对任意家具状态的控制
例如
http(`${serverIP}:8080/set/${bedroom}/${bedlight}/${open}?value=${isOpen?"true":"false"}`).request()
而在页面中,我们可以这样使用
<body><button id="bedLight" onClick="switchBedLight">switchLight</button>
</body>
<script>function updateText(){button.innerHtml=state?"trun off":"turn on";}function getBedLight(){return await request(`get/bed_room/bed_light/open`)}button = getElementById("bedLight")state = getBedLight();function switchBedLight(){await request(`set/bed_room/bed_light/open?value=${!state}`)state = getBedLight();updateText();}updateText();
</script>
对于大部分静态网页,以及程序生成的动态网页,这种方法已经够用了.
在运行时引入AI
问题
让我们尝试在运行时使用AI构建页面
工作方式为
1.前端--(用户请求)-->服务器
2.服务器--(接口清单,用户请求,系统提示词)-->LLM
3.LLM--(网页代码)-->服务器
4.服务器--(组装页面)-->前端
5.前端--(渲染呈现)-->用户
不同于在codex中让ai创建一个页面,在上述过程中,并没有程序员的介入
用户可以只能输入
"所有的灯"
然后上述链条工作,AI基于这个需求产出了页面
然后这个页面,就要在只经过了程序检查的情况下,直接渲染在浏览器中.
如果出现某个逻辑错误,用户显然不能自己打开f12修复bug.
这种需要直接与用户在运行时交互的特性
要求AI生成的网页具有尽可能高的可靠性
增加抽象层
要解决编码本身的问题,最好的方案无疑是增加抽象层.
一套抽象接口所能提供的功能几乎总是其底层的的子集
但设计良好的话,抽象接口也可以让特定的需求集合的实现变得极为简单可靠
看上述场景,我们可以发现,所有的value都遵循一定的模式,
int,bool,enum,三种类型基本可以覆盖90%的家具的状态
所以,针对床头灯的开关,以及其他什么家具的所有bool状态
我们这里可以封装一个 bool-button类
<bool-button room="bed_room" item="bed_light" value="open"/>
而bool-button的工作方式大概是:
<template><button id="button" onClick="switchBool">switchLight</button><script>var metaData=window.metaRoomData;var roomId="";var itemId="";var valueId="";function updateText(){button.innerHtml=state?"trun off":"turn on";}function getState(){return await request(`get/${roomId}/${itemId}/${valueId}`)}button = getElementById("button")state = getBedLight();function switchBool(){await request(`set/${roomId}/${itemId}/${valueId}?value=${!state}`)state = getState();updateText();}thisComponent.onInit=(){roomId=thisComponent.args.room;itmeId=thisComponent.args.item;valueId=thisComponent.args.value;if(!metaData.has(roomId,itmeId,valueId)){console.Error("err")};updateText();}</script>
</template>
我们还可以照此继续写出int-slider enum-select 等组件
他们封装了完整的业务逻辑,只需传入自己所绑定的数据ID就可以工作,并且能够检查ID是否真的存在,具有一定的纠错能力.
生成前后的工作
生成前
我们需要收集元数据,将其转换为自然语言表述,这很容易,但还是最好通过代码实现
之后我们就可以对LLM说,
在卧室(bed_room),有家具:床头灯(bed_light),空调(air_conditioner)
在厨房(kitchen),有抽油烟机(range_hood)......
我们可以先要求AI选出用户需求所涉及的家具,输出他们的id.
然后程序再从元数据中取出那些家具的具体状态
那么下一轮的提示词就是
根据用户需求:...
生成一个页面,使用给定组件
组件有...
如何使用他们...
而相关的家具与状态有...
生成格式要求...
你需要准确填写id作为组件的参数
生成后
当我们将生成请求发送给了LLM,并获取到了返回的页面,就可以从回复中提取我们需要的页面代码
可以先检验其中id的有效性,并要求LLM仔细考虑无效的id,并修改页面.这样就可以无需将元数据直接暴露给页面
最后将页面返回给前端页面,渲染给用户使用
总结
本文章仅在抽象层面做讨论,上述代码均不是最终可用的系统实现
在实际开发中使用vue实现bool-button等组件或许会是更好的选择
而如果真的存在某个需要上述设想落地的需求场景,那么安全性问题,或许实际会消耗我们更多精力
