2023年,人工智能在前端行业已崭露头角,为我们带来了前所未有的变革和机遇。
借此机遇和行业背景,遂分享下个人在微信小程序下关于“AI对话界面”的设计与实现。



一、界面要点分析

页面布局

从界面布局上看,分为聊天区和输入区,那么相应的可以采用content和footer的布局模式。 content区域即聊天区板块,footer区域即用户输入板块。

  1. content区域需要加载引导语,引导语可让用户更好的了解该AI的作用。其次需要支持预定义问题以实现快速问答。
    用户和AI的来回对话内容可视为list列表模式。
    AI对话内容从左至右渲染,同时需要支持视频、图片、链接、代码块等呈现。
    用户对话内容从右至左渲染,需要支持文字呈现。
  2. footer区域功能较简单,可采用flex布局实现左中右排列即可。
    但需要注意的就是,输入框需要采用textarea标签,以满足用户输入内容的多样性和输入换行等行为。

二、后台服务分析

目前后台服务并未采用SSE形式进行数据传输,而是将获取到的AI答复一次性返回于客户端。

且经过试验后发现,返回的内容块中包含:文字、链接和代码块。

同时打字机效果需要前端通过定时器setInterval或有限元状态机的形式实现。该项目采用前者方式实现打字机效果。效果如下图形式:
打字机效果

三、HTML布局

需要解释的是,为什么在footer区域没有采用position:fixed来将其固定在页面底部。
这是因为在某些机型上存在兼容性问题。具体表现为:当将输入框设置为fixed后,在输入框获取焦点并开始输入时,软键盘会将输入框顶起。
当输入框失去焦点后,软键盘会收回,但此时输入框仍然会停留在被顶起的位置,无法回到原来的位置。iOS 下 Input 和 fixed 的问题

以下是简约版HTML代码结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<section class="chat">
<!-- content -->
<section class="chat-center">
<!-- 聊天区 -->
<ul>
<!-- 历史记录list -->
<li class="conversation" v-for="(item,index) in oldList" :key="item.msgId">
<div v-if="item.senderRole == 1">
<p class="content">{{ item.content }}</p>
<img class='head' src="xxx" />
</div>
<div v-if="item.senderRole == 2">
<img class='head' src="xxx" />
<p :id="'hisContentId_'+index" v-html="item.content"></p>
</div>
</li>

<!-- 提示语 -->
<li class="welcomeTips">
<p>欢迎进入XXX对话页面,本次服务由ChatGPT参与提供,AI生成的内容在小部分场景下可能不一定能非常准确,如有疑问,请及时联系相关人员。</p>
</li>

<!-- 当前对话list -->
<li class="conversation" v-for="(item,index) in oldList" :key="item.msgId">
<div v-if="item.senderRole == 1">
<p class="content">{{ item.content }}</p>
<img class='head' src="xxx" />
</div>
<div v-if="item.senderRole == 2">
<img class='head' src="xxx" />
<p :id="'hisContentId_'+index" v-html="item.content"></p>
</div>
</li>
</ul>
</section>

<!-- footer -->
<section class="chatFooter" >
<textarea rows="1" type="textarea" :autosize="{'maxHeight': 100}" placeholder="请输入"></textarea>
</section>
</section>

四、JS实现主体功能

JavaScript部分已经实现了项目的大部分重要内容,包括支持预定义问题以实现快速提问、解析视频、图片和代码块,以及实现打字机效果。接下来将分段介绍这些部分的具体实现。

  1. 支持预定义问答。
    首先是支持预定义问答。预定义问答包含引导语文本块和预定义列表,其中文本块会通过定时器逐字输出,而预定义列表则会通过innerHtml方式直接输出。


  2. 解析视频、图片、链接等。
    其次是解析视频、图片和链接等内容。在AI回复中,视频、图片等内容以链接形式返回,因此只需使用正则表达式匹配http或https链接,然后根据链接尾部的类型进行if判定。最后,将对应的html标签包裹上链接,并通过innerHtml输入至对话框中。


  3. 解析代码块。
    最后是解析代码块。代码块的匹配也是固定的,只需通过正则表达式匹配“`”和“```”标记即可。匹配到的代码块内容将交由代码高亮插件输出为html形式,最后通过innerHtml渲染至对话框中。需要注意的是,由于正则表达式的特殊性,代码块需要先匹配“```”,再匹配“`”。


  4. 打字机效果。
    使用requestAnimationFrame函数对需要打印的内容做递归循环逐字打印,且在打印时设置list列表scrollTop的值,以此实现边打字边滚动的效果。requestAnimationFrame函数需做polyfill。


  5. AI回复输出:
    AI回复输出效果

五、需要注意的地方

  1. 在使用正则匹配视频、图片、链接、代码块等时需注意ios系统不支持正则的零宽断言的问题。在代码编译后真机运行时报chunk错误的情况可以观察写的正则是否支持该设备。排坑·IPhone&IOS中不兼容正则中的断言匹配
  2. 代码块高亮使用的是prism库实现。
  3. 使用ResizeObserver监听textarea因为换行产生的高度变化,从而动态设定content区域的高度值。
    这样可以确保content区域和footer区域的高度总和始终能够撑满屏幕,不会出现溢出而造成双重滚动条的情况。
    同时,使用ResizeObserver可以实时监测textarea的高度变化,确保content区域的高度能够随之调整,从而保持页面的美观性和稳定性。
    这样的做法能够有效地提升用户体验,让页面布局更加灵活和适应性更强。