自从去年11月份ChatGPT横空出世以来,其热度一直居高不下。许多开发者也纷纷借助其开放的接口,开发出各种国内版本。本文将从前端工程师的角度,探讨如何实现类似ChatGPT的对话功能。
先来看看最终的效果。这里我将源码放在文章末尾,方便大家获取。
因为这是我写在一个项目中的,单独提出来可能配色效果不同,但功能和做法只要我们掌握了,那么自己想怎么写就怎么写!
页面的布局相对简单。熟悉ChatGPT页面的同学应该知道,其布局主要采用了Flex布局。Flex布局的强大之处在于其灵活性和易用性。我主要基于Bootstrap、jQuery和Flex布局完成了这个简易版的对话功能。其中,有两个关键的地方用到了Flex布局。
Flex布局一:头像和文字的对齐
头像和文字的排列采用Flex布局,并且文字和图片顶部对齐,以避免文字过多时,文字与图片垂直居中的问题。实现这种效果,需要设置以下CSS样式:
display: flex;
align-items: flex-start;
其中,align-items: flex-start;
的作用是确保文字与图片顶部对齐。
Flex布局二(重点):搜索框的实现
第二个用到Flex布局的地方是搜索框。
许多人可能觉得这个对话框很简单。Flex布局确实能轻松实现输入框和按钮在同一行显示。但是,仔细观察ChatGPT的官方网站,你会发现其中有很多细节值得学习。
首先,输入框使用的是<textarea>
,而不是<input>
。<input>
输入的内容无法换行,而<textarea>
文本框可以。但是,使用<textarea>
会带来一个问题:如果将rows
参数设置为1,文本框的高度会非常小,无法达到ChatGPT页面的视觉效果。如果将rows
设置得更大,或者直接设置文本框的高度,又会出现输入时光标不在文本框垂直居中的问题。因此,如何解决这个问题是一个值得探讨的地方。通过观察ChatGPT页面的实现方式,我找到了解决方案。如下图所示:
你只需要将<textarea>
的边框取消掉,然后使用:focus
伪类取消掉选中时的边框效果,再在外层套一个<div>
,并设置边框,将<textarea>
文本框和按钮包裹在其中即可。
.ipt {
display: flex;
align-items: center;
position: absolute;
bottom: 60px;
margin: 0 15px;
padding-right: 15px;
border-radius: 15px;
width: calc(100% - 30px);
height: 50px;
border: 1px solid #e7eaec;
}
.ipt textarea {
resize: none;
overflow-y: auto;
border: none;
box-shadow: none;
}
.ipt textarea:focus {
border: none !important;
box-shadow: none !important;
}
最后,将这个输入框定位到页面底部即可。
JavaScript部分
在页面部分,我们需要将用户的问题以及AI的回复添加到页面上,并在添加消息后向上滚动:
// 添加用户消息到窗口
function addUserMessage(message) {
var messageElement = $('<div class="row message-bubble"><img class="chat-icon" src="' + userIcon + '"><p class="message-text">' + message + '</p></div>');
chatWindow.append(messageElement);
chatInput.val('');
chatWindow.animate({ scrollTop: chatWindow.prop('scrollHeight') }, 500);
}
// 添加回复消息到窗口
function addBotMessage(message) {
var messageElement = $('<div class="row message-bubble"><img class="chat-icon" src="' + botIcon + '"><p class="message-text">' + message + '</p></div>');
chatWindow.append(messageElement);
chatInput.val('');
chatWindow.animate({ scrollTop: chatWindow.prop('scrollHeight') }, 500);
}
这里,消息添加到页面后,会清空输入框的内容。接下来,我们需要给输入框添加一个键盘事件,即点击Enter键也可以发送消息:
// 处理 Enter 键按下
chatInput.keypress(function(e) {
if (e.which == 13) {
chatBtn.click();
}
});
最后,是发送消息与获得消息的部分:
// 处理用户输入
chatBtn.click(function() {
var message = chatInput.val();
if (message.length == 0) {
common_ops.alert("请输入内容!") // 弹窗
return
}
addUserMessage(message);
chatBtn.attr('disabled', true) // 消息发送后让提交按钮不可点击
// 发送信息到后台
$.ajax({
url: '/chat',
method: 'POST',
data: {
"prompt": JSON.stringify(message)
},
success: function(res) {
res = JSON.parse(res);
addBotMessage(res.content);
chatBtn.attr('disabled', false) // 成功接受消息后让提交按钮再次可以点击
},
error: function(jqXHR, textStatus, errorThrown) {
addBotMessage('<span style="color:red;">' + '出错啦!请稍后再试!' + '</span>');
chatBtn.attr('disabled', false)
}
});
});
这些逻辑都比较简单,就不再赘述。需要注意的是,在发送消息到后台等待响应的过程中,按钮的状态设置为不可点击,直到后台返回消息才可以进行下一次问答。但是,这里我没有处理键盘事件,也就是说你可以点击Enter键继续向后台发送消息,这是一个潜在的bug。如果不需要这个功能,可以直接去掉键盘事件。当然,也可以在发送消息到获得回答的这段时间内,像禁用发送按钮一样,禁止Enter键盘事件或解绑这个键盘事件。这些都可以自行完成。
完整代码
以下是完整的HTML代码,包含了CSS样式和JavaScript脚本:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="../../static/css/bootstrap.min.css" rel="stylesheet">
<title>chat</title>
<style>
.answer {
width: 100%;
position: relative;
height: 70vh;
}
.ipt {
display: flex;
align-items: center;
position: absolute;
bottom: 60px;
margin: 0 15px;
padding-right: 15px;
border-radius: 15px;
width: calc(100% - 30px);
height: 50px;
border: 1px solid #e7eaec;
}
.ipt textarea {
resize: none;
overflow-y: auto;
border: none;
box-shadow: none;
}
.ipt textarea:focus {
border: none !important;
box-shadow: none !important;
}
#chatWindow {
max-height: calc(70vh - 120px);
height: auto;
overflow-y: auto;
}
.message-bubble {
padding: 10px;
margin: 5px;
display: flex;
align-items: flex-start;
border-bottom: 1px dashed #e7eaec;
}
程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。